Merge "Revert "Speed up tests by injecting input events asynchronously"" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 7805b32..25f5412 100644
--- a/Android.mk
+++ b/Android.mk
@@ -28,6 +28,7 @@
     androidx.recyclerview_recyclerview \
     androidx.dynamicanimation_dynamicanimation \
     androidx.preference_preference \
+    androidx.slice_slice-view \
     iconloader_base
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
@@ -48,7 +49,7 @@
 LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_MODULE := Launcher3CommonDepsLib
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_MANIFEST_FILE := AndroidManifest-common.xml
@@ -77,7 +78,7 @@
 LOCAL_PROGUARD_ENABLED := disabled
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_PACKAGE_NAME := Launcher3
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_SYSTEM_EXT_MODULE := true
@@ -108,7 +109,7 @@
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
 
 LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 21
+LOCAL_MIN_SDK_VERSION := 26
 LOCAL_PACKAGE_NAME := Launcher3Go
 LOCAL_PRIVILEGED_MODULE := true
 LOCAL_SYSTEM_EXT_MODULE := true
@@ -149,12 +150,9 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, src_shortcuts_overrides)
 
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
 LOCAL_PROGUARD_ENABLED := disabled
 
 
@@ -183,9 +181,7 @@
 LOCAL_OVERRIDES_PACKAGES := Home Launcher2 Launcher3
 LOCAL_REQUIRED_MODULES := privapp_whitelist_com.android.launcher3
 
-LOCAL_RESOURCE_DIR := \
-    $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/quickstep/res
 
 LOCAL_FULL_LIBS_MANIFEST_FILES := \
     $(LOCAL_PATH)/quickstep/AndroidManifest-launcher.xml \
@@ -220,12 +216,10 @@
 LOCAL_SRC_FILES := \
     $(call all-java-files-under, src) \
     $(call all-java-files-under, quickstep/src) \
-    $(call all-java-files-under, quickstep/recents_ui_overrides/src) \
     $(call all-java-files-under, go/src)
 
 LOCAL_RESOURCE_DIR := \
     $(LOCAL_PATH)/quickstep/res \
-    $(LOCAL_PATH)/quickstep/recents_ui_overrides/res \
     $(LOCAL_PATH)/go/res
 
 LOCAL_PROGUARD_FLAG_FILES := proguard.flags
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 19a16e3..97e3786 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -29,12 +29,6 @@
     at compile time. Note that the components defined in AndroidManifest.xml are also required,
     with some minor changed based on the derivative app.
     -->
-    <permission
-        android:name="com.android.launcher.permission.INSTALL_SHORTCUT"
-        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
-        android:protectionLevel="dangerous"
-        android:label="@string/permlab_install_shortcut"
-        android:description="@string/permdesc_install_shortcut" />
 
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
@@ -79,17 +73,6 @@
         android:restoreAnyVersion="true"
         android:supportsRtl="true" >
 
-        <!-- Intent received used to install shortcuts from other applications -->
-        <receiver
-            android:name="com.android.launcher3.InstallShortcutReceiver"
-            android:permission="com.android.launcher.permission.INSTALL_SHORTCUT"
-            android:exported="true"
-            android:enabled="@bool/enable_install_shortcut_api" >
-            <intent-filter>
-                <action android:name="com.android.launcher.action.INSTALL_SHORTCUT" />
-            </intent-filter>
-        </receiver>
-
         <!-- Intent received when a session is committed -->
         <receiver
             android:name="com.android.launcher3.SessionCommitReceiver"
@@ -116,7 +99,6 @@
         <service
             android:name="com.android.launcher3.notification.NotificationListener"
             android:label="@string/notification_dots_service_title"
-            android:enabled="@bool/notification_dots_enabled"
             android:exported="true"
             android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
             <intent-filter>
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 4664c93..97bce9c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -20,7 +20,7 @@
 <manifest
     xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.launcher3">
-    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="25"/>
+    <uses-sdk android:targetSdkVersion="29" android:minSdkVersion="26"/>
     <!--
     Manifest entries specific to Launcher3. This is merged with AndroidManifest-common.xml.
     Refer comments around specific entries on how to extend individual components.
diff --git a/OWNERS b/OWNERS
index 3069afa..1d6ad8c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,6 +28,7 @@
 peanutbutter@google.com
 xuqiu@google.com
 sreyasr@google.com
+thiruram@google.com
 
 per-file FeatureFlags.java, globs = set noparent
 per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, zakcohen@google.com, mrcasey@google.com, adamcohen@google.com, hyunyoungs@google.com
diff --git a/go/AndroidManifest.xml b/go/AndroidManifest.xml
index f84a82e..f36439d 100644
--- a/go/AndroidManifest.xml
+++ b/go/AndroidManifest.xml
@@ -46,6 +46,12 @@
             tools:node="replace" >
         </activity>
 
+        <service
+            android:name="com.android.launcher3.notification.NotificationListener"
+            android:label="@string/notification_dots_service_title"
+            android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
+            android:enabled="false"
+            tools:node="replace" />
     </application>
 
 </manifest>
diff --git a/go/res/values-v26/bools.xml b/go/res/values-v26/bools.xml
deleted file mode 100644
index cc4a7ba..0000000
--- a/go/res/values-v26/bools.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 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.
-*/
--->
-
-<resources>
-    <bool name="notification_badging_enabled">false</bool>
-</resources>
\ No newline at end of file
diff --git a/go/res/values/dimens.xml b/go/res/values/dimens.xml
deleted file mode 100644
index f1b1053..0000000
--- a/go/res/values/dimens.xml
+++ /dev/null
@@ -1,20 +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.
--->
-
-<resources>
-    <!-- Dynamic Grid -->
-    <dimen name="dynamic_grid_hotseat_size">60dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LoaderResults.java
index 7130531..5f71061 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LoaderResults.java
@@ -20,7 +20,6 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.util.LooperExecutor;
 
 /**
  * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
@@ -29,12 +28,7 @@
 
     public LoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, Callbacks[] callbacks) {
-        this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
-    }
-
-    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
-            AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
-        super(app, dataModel, allAppsList, callbacks, executor);
+        super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
     }
 
     @Override
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 3b3dc01..89b3831 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -41,6 +41,7 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = true;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = true;
 
     private static final ArrayList<WidgetListRowEntry> EMPTY_WIDGET_LIST = new ArrayList<>();
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
deleted file mode 100644
index 7b8070a..0000000
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ /dev/null
@@ -1,317 +0,0 @@
-<<<<<<< HEAD   (cc6caf Merge "Let launcher to provide its own OverscrollPlugin" int)
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.icons.cache;
-
-import android.content.ComponentName;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.SparseBooleanArray;
-
-import com.android.launcher3.icons.cache.BaseIconCache.IconDB;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.Stack;
-
-/**
- * Utility class to handle updating the Icon cache
- */
-public class IconCacheUpdateHandler {
-
-    private static final String TAG = "IconCacheUpdateHandler";
-
-    /**
-     * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}.
-     * This mode is used for the first run.
-     */
-    private static final boolean MODE_SET_INVALID_ITEMS = true;
-
-    /**
-     * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all
-     * subsequent runs, which essentially acts as set-union of all valid items.
-     */
-    private static final boolean MODE_CLEAR_VALID_ITEMS = false;
-
-    private static final Object ICON_UPDATE_TOKEN = new Object();
-
-    private final HashMap<String, PackageInfo> mPkgInfoMap;
-    private final BaseIconCache mIconCache;
-
-    private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>();
-
-    private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
-    private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
-
-    IconCacheUpdateHandler(BaseIconCache cache) {
-        mIconCache = cache;
-
-        mPkgInfoMap = new HashMap<>();
-
-        // Remove all active icon update tasks.
-        mIconCache.mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
-
-        createPackageInfoMap();
-    }
-
-    /**
-     * Sets a package to ignore for processing
-     */
-    public void addPackagesToIgnore(UserHandle userHandle, String packageName) {
-        Set<String> packages = mPackagesToIgnore.get(userHandle);
-        if (packages == null) {
-            packages = new HashSet<>();
-            mPackagesToIgnore.put(userHandle, packages);
-        }
-        packages.add(packageName);
-    }
-
-    private void createPackageInfoMap() {
-        PackageManager pm = mIconCache.mPackageManager;
-        for (PackageInfo info :
-                pm.getInstalledPackages(PackageManager.MATCH_UNINSTALLED_PACKAGES)) {
-            mPkgInfoMap.put(info.packageName, info);
-        }
-    }
-
-    /**
-     * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
-     * the DB and are updated.
-     * @return The set of packages for which icons have updated.
-     */
-    public <T> void updateIcons(List<T> apps, CachingLogic<T> cachingLogic,
-            OnUpdateCallback onUpdateCallback) {
-        // Filter the list per user
-        HashMap<UserHandle, HashMap<ComponentName, T>> userComponentMap = new HashMap<>();
-        int count = apps.size();
-        for (int i = 0; i < count; i++) {
-            T app = apps.get(i);
-            UserHandle userHandle = cachingLogic.getUser(app);
-            HashMap<ComponentName, T> componentMap = userComponentMap.get(userHandle);
-            if (componentMap == null) {
-                componentMap = new HashMap<>();
-                userComponentMap.put(userHandle, componentMap);
-            }
-            componentMap.put(cachingLogic.getComponent(app), app);
-        }
-
-        for (Entry<UserHandle, HashMap<ComponentName, T>> entry : userComponentMap.entrySet()) {
-            updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback);
-        }
-
-        // From now on, clear every valid item from the global valid map.
-        mFilterMode = MODE_CLEAR_VALID_ITEMS;
-    }
-
-    /**
-     * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
-     * the DB and are updated.
-     * @return The set of packages for which icons have updated.
-     */
-    @SuppressWarnings("unchecked")
-    private <T> void updateIconsPerUser(UserHandle user, HashMap<ComponentName, T> componentMap,
-            CachingLogic<T> cachingLogic, OnUpdateCallback onUpdateCallback) {
-        Set<String> ignorePackages = mPackagesToIgnore.get(user);
-        if (ignorePackages == null) {
-            ignorePackages = Collections.emptySet();
-        }
-        long userSerial = mIconCache.getSerialNumberForUser(user);
-
-        Stack<T> appsToUpdate = new Stack<>();
-
-        try (Cursor c = mIconCache.mIconDb.query(
-                new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
-                        IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
-                        IconDB.COLUMN_SYSTEM_STATE},
-                IconDB.COLUMN_USER + " = ? ",
-                new String[]{Long.toString(userSerial)})) {
-
-            final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
-            final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
-            final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
-            final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
-            final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
-
-            while (c.moveToNext()) {
-                String cn = c.getString(indexComponent);
-                ComponentName component = ComponentName.unflattenFromString(cn);
-                PackageInfo info = mPkgInfoMap.get(component.getPackageName());
-
-                int rowId = c.getInt(rowIndex);
-                if (info == null) {
-                    if (!ignorePackages.contains(component.getPackageName())) {
-
-                        if (mFilterMode == MODE_SET_INVALID_ITEMS) {
-                            mIconCache.remove(component, user);
-                            mItemsToDelete.put(rowId, true);
-                        }
-                    }
-                    continue;
-                }
-                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
-                    // Application is not present
-                    continue;
-                }
-
-                long updateTime = c.getLong(indexLastUpdate);
-                int version = c.getInt(indexVersion);
-                T app = componentMap.remove(component);
-                if (version == info.versionCode
-                        && updateTime == cachingLogic.getLastUpdatedTime(app, info)
-                        && TextUtils.equals(c.getString(systemStateIndex),
-                                mIconCache.getIconSystemState(info.packageName))) {
-
-                    if (mFilterMode == MODE_CLEAR_VALID_ITEMS) {
-                        mItemsToDelete.put(rowId, false);
-                    }
-                    continue;
-                }
-
-                if (app == null) {
-                    if (mFilterMode == MODE_SET_INVALID_ITEMS) {
-                        mIconCache.remove(component, user);
-                        mItemsToDelete.put(rowId, true);
-                    }
-                } else {
-                    appsToUpdate.add(app);
-                }
-            }
-        } catch (SQLiteException e) {
-            Log.d(TAG, "Error reading icon cache", e);
-            // Continue updating whatever we have read so far
-        }
-
-        // Insert remaining apps.
-        if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
-            Stack<T> appsToAdd = new Stack<>();
-            appsToAdd.addAll(componentMap.values());
-            new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic,
-                    onUpdateCallback).scheduleNext();
-        }
-    }
-
-    /**
-     * Commits all updates as part of the update handler to disk. Not more calls should be made
-     * to this class after this.
-     */
-    public void finish() {
-        // Commit all deletes
-        int deleteCount = 0;
-        StringBuilder queryBuilder = new StringBuilder()
-                .append(IconDB.COLUMN_ROWID)
-                .append(" IN (");
-
-        int count = mItemsToDelete.size();
-        for (int i = 0;  i < count; i++) {
-            if (mItemsToDelete.valueAt(i)) {
-                if (deleteCount > 0) {
-                    queryBuilder.append(", ");
-                }
-                queryBuilder.append(mItemsToDelete.keyAt(i));
-                deleteCount++;
-            }
-        }
-        queryBuilder.append(')');
-
-        if (deleteCount > 0) {
-            mIconCache.mIconDb.delete(queryBuilder.toString(), null);
-        }
-    }
-
-    /**
-     * A runnable that updates invalid icons and adds missing icons in the DB for the provided
-     * LauncherActivityInfo list. Items are updated/added one at a time, so that the
-     * worker thread doesn't get blocked.
-     */
-    private class SerializedIconUpdateTask<T> implements Runnable {
-        private final long mUserSerial;
-        private final UserHandle mUserHandle;
-        private final Stack<T> mAppsToAdd;
-        private final Stack<T> mAppsToUpdate;
-        private final CachingLogic<T> mCachingLogic;
-        private final HashSet<String> mUpdatedPackages = new HashSet<>();
-        private final OnUpdateCallback mOnUpdateCallback;
-
-        SerializedIconUpdateTask(long userSerial, UserHandle userHandle,
-                Stack<T> appsToAdd, Stack<T> appsToUpdate, CachingLogic<T> cachingLogic,
-                OnUpdateCallback onUpdateCallback) {
-            mUserHandle = userHandle;
-            mUserSerial = userSerial;
-            mAppsToAdd = appsToAdd;
-            mAppsToUpdate = appsToUpdate;
-            mCachingLogic = cachingLogic;
-            mOnUpdateCallback = onUpdateCallback;
-        }
-
-        @Override
-        public void run() {
-            if (!mAppsToUpdate.isEmpty()) {
-                T app = mAppsToUpdate.pop();
-                String pkg = mCachingLogic.getComponent(app).getPackageName();
-                PackageInfo info = mPkgInfoMap.get(pkg);
-
-                mIconCache.addIconToDBAndMemCache(
-                        app, mCachingLogic, info, mUserSerial, true /*replace existing*/);
-                mUpdatedPackages.add(pkg);
-
-                if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
-                    // No more app to update. Notify callback.
-                    mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle);
-                }
-
-                // Let it run one more time.
-                scheduleNext();
-            } else if (!mAppsToAdd.isEmpty()) {
-                T app = mAppsToAdd.pop();
-                PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName());
-                // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
-                // app should have package info, this is not guaranteed by the api
-                if (info != null) {
-                    mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info,
-                            mUserSerial, false /*replace existing*/);
-                }
-
-                if (!mAppsToAdd.isEmpty()) {
-                    scheduleNext();
-                }
-            }
-        }
-
-        public void scheduleNext() {
-            mIconCache.mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN,
-                    SystemClock.uptimeMillis() + 1);
-        }
-    }
-
-    public interface OnUpdateCallback {
-
-        void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandle user);
-    }
-}
-=======
->>>>>>> CHANGE (805b52 Removes iconloaderlib from Launcher3.)
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
deleted file mode 100644
index f03f118..0000000
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <color name="chip_hint_foreground_color">#fff</color>
-    <color name="chip_scrim_start_color">#39000000</color>
-
-    <color name="all_apps_label_text">#61000000</color>
-    <color name="all_apps_label_text_dark">#61FFFFFF</color>
-    <color name="all_apps_prediction_row_separator">#3c000000</color>
-    <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
-</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
deleted file mode 100644
index 9266b06..0000000
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <dimen name="chip_hint_border_width">1dp</dimen>
-    <dimen name="chip_hint_corner_radius">20dp</dimen>
-    <dimen name="chip_hint_outer_padding">20dp</dimen>
-    <dimen name="chip_hint_start_padding">10dp</dimen>
-    <dimen name="chip_hint_end_padding">12dp</dimen>
-    <dimen name="chip_hint_horizontal_margin">20dp</dimen>
-    <dimen name="chip_hint_vertical_offset">16dp</dimen>
-    <dimen name="chip_hint_elevation">2dp</dimen>
-    <dimen name="chip_icon_size">16dp</dimen>
-    <dimen name="chip_text_height">26dp</dimen>
-    <dimen name="chip_text_top_padding">4dp</dimen>
-    <dimen name="chip_text_start_padding">10dp</dimen>
-    <dimen name="chip_text_size">14sp</dimen>
-
-    <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
-    <dimen name="all_apps_label_top_padding">16dp</dimen>
-    <dimen name="all_apps_label_bottom_padding">8dp</dimen>
-    <dimen name="all_apps_label_text_size">14sp</dimen>
-
-    <!-- Minimum distance to swipe to trigger accessibility gesture -->
-    <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
-</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/REMOVED.txt b/quickstep/recents_ui_overrides/src/REMOVED.txt
new file mode 100644
index 0000000..c3a3eaf
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/REMOVED.txt
@@ -0,0 +1,2 @@
+Temp file to prevent build breakage.
+Will be removed in followup cl.
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
deleted file mode 100644
index d200868..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.appprediction;
-
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.util.ComponentKey;
-
-public class ComponentKeyMapper {
-
-    protected final ComponentKey componentKey;
-    private final DynamicItemCache mCache;
-
-    public ComponentKeyMapper(ComponentKey key, DynamicItemCache cache) {
-        componentKey = key;
-        mCache = cache;
-    }
-
-    public String getPackage() {
-        return componentKey.componentName.getPackageName();
-    }
-
-    public String getComponentClass() {
-        return componentKey.componentName.getClassName();
-    }
-
-    public ComponentKey getComponentKey() {
-        return componentKey;
-    }
-
-    @Override
-    public String toString() {
-        return componentKey.toString();
-    }
-
-    public ItemInfoWithIcon getApp(AllAppsStore store) {
-        AppInfo item = store.getApp(componentKey);
-        if (item != null) {
-            return item;
-        } else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
-            return mCache.getInstantApp(componentKey.componentName.getPackageName());
-        } else {
-            return mCache.getShortcutInfo(componentKey);
-        }
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
deleted file mode 100644
index ab96b1340..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.appprediction;
-
-import static android.content.pm.PackageManager.MATCH_INSTANT;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ShortcutInfo;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.InstantAppResolver;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Utility class which loads and caches predicted items like instant apps and shortcuts, before
- * they can be displayed on the UI
- */
-public class DynamicItemCache {
-
-    private static final String TAG = "DynamicItemCache";
-    private static final boolean DEBUG = false;
-    private static final String DEFAULT_URL = "default-url";
-
-    private static final int BG_MSG_LOAD_SHORTCUTS = 1;
-    private static final int BG_MSG_LOAD_INSTANT_APPS = 2;
-
-    private static final int UI_MSG_UPDATE_SHORTCUTS = 1;
-    private static final int UI_MSG_UPDATE_INSTANT_APPS = 2;
-
-    private final Context mContext;
-    private final Handler mWorker;
-    private final Handler mUiHandler;
-    private final InstantAppResolver mInstantAppResolver;
-    private final Runnable mOnUpdateCallback;
-    private final IconCache mIconCache;
-
-    private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
-    private final Map<String, InstantAppItemInfo> mInstantApps;
-
-    public DynamicItemCache(Context context, Runnable onUpdateCallback) {
-        mContext = context;
-        mWorker = new Handler(MODEL_EXECUTOR.getLooper(), this::handleWorkerMessage);
-        mUiHandler = new Handler(Looper.getMainLooper(), this::handleUiMessage);
-        mInstantAppResolver = InstantAppResolver.newInstance(context);
-        mOnUpdateCallback = onUpdateCallback;
-        mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
-
-        mShortcuts = new HashMap<>();
-        mInstantApps = new HashMap<>();
-    }
-
-    public void cacheItems(List<ShortcutKey> shortcutKeys, List<String> pkgNames) {
-        if (!shortcutKeys.isEmpty()) {
-            mWorker.removeMessages(BG_MSG_LOAD_SHORTCUTS);
-            Message.obtain(mWorker, BG_MSG_LOAD_SHORTCUTS, shortcutKeys).sendToTarget();
-        }
-        if (!pkgNames.isEmpty()) {
-            mWorker.removeMessages(BG_MSG_LOAD_INSTANT_APPS);
-            Message.obtain(mWorker, BG_MSG_LOAD_INSTANT_APPS, pkgNames).sendToTarget();
-        }
-    }
-
-    private boolean handleWorkerMessage(Message msg) {
-        switch (msg.what) {
-            case BG_MSG_LOAD_SHORTCUTS: {
-                List<ShortcutKey> shortcutKeys = msg.obj != null ?
-                        (List<ShortcutKey>) msg.obj : Collections.EMPTY_LIST;
-                Map<ShortcutKey, WorkspaceItemInfo> shortcutKeyAndInfos = new ArrayMap<>();
-                for (ShortcutKey shortcutKey : shortcutKeys) {
-                    WorkspaceItemInfo workspaceItemInfo = loadShortcutWorker(shortcutKey);
-                    if (workspaceItemInfo != null) {
-                        shortcutKeyAndInfos.put(shortcutKey, workspaceItemInfo);
-                    }
-                }
-                Message.obtain(mUiHandler, UI_MSG_UPDATE_SHORTCUTS, shortcutKeyAndInfos)
-                        .sendToTarget();
-                return true;
-            }
-            case BG_MSG_LOAD_INSTANT_APPS: {
-                List<String> pkgNames = msg.obj != null ?
-                        (List<String>) msg.obj : Collections.EMPTY_LIST;
-                List<InstantAppItemInfo> instantAppItemInfos = new ArrayList<>();
-                for (String pkgName : pkgNames) {
-                    InstantAppItemInfo instantAppItemInfo = loadInstantApp(pkgName);
-                    if (instantAppItemInfo != null) {
-                        instantAppItemInfos.add(instantAppItemInfo);
-                    }
-                }
-                Message.obtain(mUiHandler, UI_MSG_UPDATE_INSTANT_APPS, instantAppItemInfos)
-                        .sendToTarget();
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private boolean handleUiMessage(Message msg) {
-        switch (msg.what) {
-            case UI_MSG_UPDATE_SHORTCUTS: {
-                mShortcuts.clear();
-                mShortcuts.putAll((Map<ShortcutKey, WorkspaceItemInfo>) msg.obj);
-                mOnUpdateCallback.run();
-                return true;
-            }
-            case UI_MSG_UPDATE_INSTANT_APPS: {
-                List<InstantAppItemInfo> instantAppItemInfos = (List<InstantAppItemInfo>) msg.obj;
-                mInstantApps.clear();
-                for (InstantAppItemInfo instantAppItemInfo : instantAppItemInfos) {
-                    mInstantApps.put(instantAppItemInfo.getTargetComponent().getPackageName(),
-                            instantAppItemInfo);
-                }
-                mOnUpdateCallback.run();
-                if (DEBUG) {
-                    Log.d(TAG, String.format("Cache size: %d, Cache: %s",
-                            mInstantApps.size(), mInstantApps.toString()));
-                }
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    @WorkerThread
-    private WorkspaceItemInfo loadShortcutWorker(ShortcutKey shortcutKey) {
-        List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
-        if (!details.isEmpty()) {
-            WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
-            mIconCache.getShortcutIcon(si, details.get(0));
-            return si;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "No shortcut found: " + shortcutKey.toString());
-        }
-        return null;
-    }
-
-    private InstantAppItemInfo loadInstantApp(String pkgName) {
-        PackageManager pm = mContext.getPackageManager();
-
-        try {
-            ApplicationInfo ai = pm.getApplicationInfo(pkgName, 0);
-            if (!mInstantAppResolver.isInstantApp(ai)) {
-                return null;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            return null;
-        }
-
-        String url = retrieveDefaultUrl(pkgName, pm);
-        if (url == null) {
-            Log.w(TAG, "no default-url available for pkg " + pkgName);
-            return null;
-        }
-
-        Intent intent = new Intent(Intent.ACTION_VIEW)
-                .addCategory(Intent.CATEGORY_BROWSABLE)
-                .setData(Uri.parse(url));
-        InstantAppItemInfo info = new InstantAppItemInfo(intent, pkgName);
-        IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
-        iconCache.getTitleAndIcon(info, false);
-        if (info.bitmap.icon == null || iconCache.isDefaultIcon(info.bitmap, info.user)) {
-            return null;
-        }
-        return info;
-    }
-
-    @Nullable
-    public static String retrieveDefaultUrl(String pkgName, PackageManager pm) {
-        Intent mainIntent = new Intent().setAction(Intent.ACTION_MAIN)
-                .addCategory(Intent.CATEGORY_LAUNCHER).setPackage(pkgName);
-        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
-                mainIntent, MATCH_INSTANT | PackageManager.GET_META_DATA);
-        String url = null;
-        for (ResolveInfo resolveInfo : resolveInfos) {
-            if (resolveInfo.activityInfo.metaData != null
-                    && resolveInfo.activityInfo.metaData.containsKey(DEFAULT_URL)) {
-                url = resolveInfo.activityInfo.metaData.getString(DEFAULT_URL);
-            }
-        }
-        return url;
-    }
-
-    @UiThread
-    public InstantAppItemInfo getInstantApp(String pkgName) {
-        return mInstantApps.get(pkgName);
-    }
-
-    @MainThread
-    public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
-        return mShortcuts.get(key);
-    }
-
-    /**
-     * requests and caches icons for app targets
-     */
-    public void updateDependencies(List<ComponentKeyMapper> componentKeyMappers,
-            AllAppsStore appsStore, IconCache.ItemInfoUpdateReceiver callback, int itemCount) {
-        List<String> instantAppsToLoad = new ArrayList<>();
-        List<ShortcutKey> shortcutsToLoad = new ArrayList<>();
-        int total = componentKeyMappers.size();
-        for (int i = 0, count = 0; i < total && count < itemCount; i++) {
-            ComponentKeyMapper mapper = componentKeyMappers.get(i);
-            // Update instant apps
-            if (COMPONENT_CLASS_MARKER.equals(mapper.getComponentClass())) {
-                instantAppsToLoad.add(mapper.getPackage());
-                count++;
-            } else if (mapper.getComponentKey() instanceof ShortcutKey) {
-                shortcutsToLoad.add((ShortcutKey) mapper.getComponentKey());
-                count++;
-            } else {
-                // Reload high res icon
-                AppInfo info = (AppInfo) mapper.getApp(appsStore);
-                if (info != null) {
-                    if (info.usingLowResIcon()) {
-                        mIconCache.updateIconInBackground(callback, info);
-                    }
-                    count++;
-                }
-            }
-        }
-        cacheItems(shortcutsToLoad, instantAppsToLoad);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
deleted file mode 100644
index 8cabe3d..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionAppTracker.java
+++ /dev/null
@@ -1,321 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.appprediction;
-
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.annotation.TargetApi;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetEvent;
-import android.app.prediction.AppTargetId;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.os.Process;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
-import com.android.launcher3.logger.LauncherAtom.FolderContainer;
-import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
-import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
-import com.android.launcher3.logging.StatsLogManager.EventEnum;
-import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.pm.UserCache;
-import com.android.quickstep.logging.StatsLogCompatManager;
-import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
-
-import java.util.Locale;
-import java.util.function.Predicate;
-
-/**
- * Subclass of app tracker which publishes the data to the prediction engine and gets back results.
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public class PredictionAppTracker extends AppLaunchTracker implements StatsLogConsumer {
-
-    private static final String TAG = "PredictionAppTracker";
-    private static final boolean DBG = false;
-
-    private static final int MSG_INIT = 0;
-    private static final int MSG_DESTROY = 1;
-    private static final int MSG_LAUNCH = 2;
-    private static final int MSG_PREDICT = 3;
-
-    protected final Context mContext;
-    private final Handler mMessageHandler;
-
-    // Accessed only on worker thread
-    private AppPredictor mHomeAppPredictor;
-
-    public PredictionAppTracker(Context context) {
-        mContext = context;
-        mMessageHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessage);
-        InvariantDeviceProfile.INSTANCE.get(mContext).addOnChangeListener(this::onIdpChanged);
-
-        mMessageHandler.sendEmptyMessage(MSG_INIT);
-    }
-
-    @UiThread
-    private void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
-            // Reinitialize everything
-            mMessageHandler.sendEmptyMessage(MSG_INIT);
-        }
-    }
-
-    @WorkerThread
-    private void destroy() {
-        if (mHomeAppPredictor != null) {
-            mHomeAppPredictor.destroy();
-            mHomeAppPredictor = null;
-        }
-        StatsLogCompatManager.LOGS_CONSUMER.remove(this);
-    }
-
-    @WorkerThread
-    private AppPredictor createPredictor(Client client, int count) {
-        AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
-
-        if (apm == null) {
-            return null;
-        }
-
-        AppPredictor predictor = apm.createAppPredictionSession(
-                new AppPredictionContext.Builder(mContext)
-                        .setUiSurface(client.id)
-                        .setPredictedTargetCount(count)
-                        .setExtras(getAppPredictionContextExtras(client))
-                        .build());
-        predictor.registerPredictionUpdates(mContext.getMainExecutor(),
-                PredictionUiStateManager.INSTANCE.get(mContext).appPredictorCallback(client));
-        predictor.requestPredictionUpdate();
-        return predictor;
-    }
-
-    /**
-     * Override to add custom extras.
-     */
-    @WorkerThread
-    @Nullable
-    public Bundle getAppPredictionContextExtras(Client client) {
-        return null;
-    }
-
-    @WorkerThread
-    private boolean handleMessage(Message msg) {
-        switch (msg.what) {
-            case MSG_INIT: {
-                // Destroy any existing clients
-                destroy();
-
-                // Initialize the clients
-                int count = InvariantDeviceProfile.INSTANCE.get(mContext).numAllAppsColumns;
-                mHomeAppPredictor = createPredictor(Client.HOME, count);
-                StatsLogCompatManager.LOGS_CONSUMER.add(this);
-                return true;
-            }
-            case MSG_DESTROY: {
-                destroy();
-                return true;
-            }
-            case MSG_LAUNCH: {
-                if (mHomeAppPredictor != null) {
-                    mHomeAppPredictor.notifyAppTargetEvent((AppTargetEvent) msg.obj);
-                }
-                return true;
-            }
-            case MSG_PREDICT: {
-                if (mHomeAppPredictor != null) {
-                    mHomeAppPredictor.requestPredictionUpdate();
-                }
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    @UiThread
-    public void onReturnedToHome() {
-        String client = Client.HOME.id;
-        mMessageHandler.removeMessages(MSG_PREDICT, client);
-        Message.obtain(mMessageHandler, MSG_PREDICT, client).sendToTarget();
-        if (DBG) {
-            Log.d(TAG, String.format("Sent immediate message to update %s", client));
-        }
-    }
-
-    @AnyThread
-    private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId) {
-        AppTarget target = toAppTarget(atomInfo);
-        if (target != null) {
-            AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
-                    .setLaunchLocation(getContainer(atomInfo))
-                    .build();
-            Message.obtain(mMessageHandler, MSG_LAUNCH, event).sendToTarget();
-        }
-    }
-
-    @Override
-    public void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
-        if (event == LAUNCHER_APP_LAUNCH_TAP
-                || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
-                || event == LAUNCHER_TASK_LAUNCH_TAP
-                || event == LAUNCHER_QUICKSWITCH_RIGHT
-                || event == LAUNCHER_QUICKSWITCH_LEFT) {
-            sendEvent(atomInfo, AppTargetEvent.ACTION_LAUNCH);
-        } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
-            sendEvent(atomInfo, AppTargetEvent.ACTION_DISMISS);
-        }
-    }
-
-    @Nullable
-    private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
-        UserHandle userHandle = Process.myUserHandle();
-        if (info.getIsWork()) {
-            userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
-                    .filter(((Predicate<UserHandle>) userHandle::equals).negate())
-                    .findAny()
-                    .orElse(null);
-        }
-        if (userHandle == null) {
-            return null;
-        }
-        ComponentName cn = null;
-        String id = null;
-
-        switch (info.getItemCase()) {
-            case APPLICATION: {
-                LauncherAtom.Application app = info.getApplication();
-                if ((cn = parseNullable(app.getComponentName())) != null) {
-                    id = "app:" + cn.getPackageName();
-                }
-                break;
-            }
-            case SHORTCUT: {
-                LauncherAtom.Shortcut si = info.getShortcut();
-                if (!TextUtils.isEmpty(si.getShortcutId())
-                        && (cn = parseNullable(si.getShortcutName())) != null) {
-                    id = "shortcut:" + si.getShortcutId();
-                }
-                break;
-            }
-            case WIDGET: {
-                LauncherAtom.Widget widget = info.getWidget();
-                if ((cn = parseNullable(widget.getComponentName())) != null) {
-                    id = "widget:" + cn.getPackageName();
-                }
-                break;
-            }
-            case TASK: {
-                LauncherAtom.Task task = info.getTask();
-                if ((cn = parseNullable(task.getComponentName())) != null) {
-                    id = "app:" + cn.getPackageName();
-                }
-                break;
-            }
-            case FOLDER_ICON: {
-                id = "folder:" + SystemClock.uptimeMillis();
-                cn = new ComponentName(mContext.getPackageName(), "#folder");
-            }
-        }
-        if (id != null && cn != null) {
-            return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
-                    .setClassName(cn.getClassName())
-                    .build();
-        }
-        return null;
-    }
-
-    private String getContainer(LauncherAtom.ItemInfo info) {
-        ContainerInfo ci = info.getContainerInfo();
-        switch (ci.getContainerCase()) {
-            case WORKSPACE: {
-                // In case the item type is not widgets, the spaceX and spanY default to 1.
-                int spanX = info.getWidget().getSpanX();
-                int spanY = info.getWidget().getSpanY();
-                return getWorkspaceContainerString(ci.getWorkspace(), spanX, spanY);
-            }
-            case HOTSEAT: {
-                return getHotseatContainerString(ci.getHotseat());
-            }
-            case TASK_SWITCHER_CONTAINER: {
-                return "task-switcher";
-            }
-            case ALL_APPS_CONTAINER: {
-                return "all-apps";
-            }
-            case SEARCH_RESULT_CONTAINER: {
-                return "search-results";
-            }
-            case PREDICTED_HOTSEAT_CONTAINER: {
-                return "predictions/hotseat";
-            }
-            case PREDICTION_CONTAINER: {
-                return "predictions";
-            }
-            case FOLDER: {
-                FolderContainer fc = ci.getFolder();
-                switch (fc.getParentContainerCase()) {
-                    case WORKSPACE:
-                        return "folder/" + getWorkspaceContainerString(fc.getWorkspace(), 1, 1);
-                    case HOTSEAT:
-                        return "folder/" + getHotseatContainerString(fc.getHotseat());
-                }
-                return "folder";
-            }
-        }
-        return "";
-    }
-
-    private static String getWorkspaceContainerString(WorkspaceContainer wc, int spanX, int spanY) {
-        return String.format(Locale.ENGLISH, "workspace/%d/[%d,%d]/[%d,%d]",
-                wc.getPageIndex(), wc.getGridX(), wc.getGridY(), spanX, spanY);
-    }
-
-    private static String getHotseatContainerString(HotseatContainer hc) {
-        return String.format(Locale.ENGLISH, "hotseat/%d", hc.getIndex());
-    }
-
-    private static ComponentName parseNullable(String componentNameString) {
-        return TextUtils.isEmpty(componentNameString)
-                ? null : ComponentName.unflattenFromString(componentNameString);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
deleted file mode 100644
index 830c103..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.appprediction;
-
-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.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.content.ComponentName;
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsStore.OnUpdateListener;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
-import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.MainThreadInitializedObject;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-
-/**
- * Handler responsible to updating the UI due to predicted apps changes. Operations:
- * 1) Pushes the predicted apps to all-apps. If all-apps is visible, waits until it becomes
- * invisible again before applying the changes. This ensures that the UI does not change abruptly
- * in front of the user, even if an app launched and user pressed back button to return to the
- * all-apps UI again.
- * 2) Prefetch high-res icons for predicted apps. This ensures that we have the icons in memory
- * even if all-apps is not opened as they are shown in search UI as well
- * 3) Load instant app if it is not already in memory. As predictions are persisted on disk,
- * instant app will not be in memory when launcher starts.
- * 4) Maintains the current active client id (for the predictions) and all updates are performed on
- * that client id.
- */
-public class PredictionUiStateManager implements StateListener<LauncherState>,
-        ItemInfoUpdateReceiver, OnIDPChangeListener, OnUpdateListener {
-
-    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
-
-    // TODO (b/129421797): Update the client constants
-    public enum Client {
-        HOME("home");
-
-        public final String id;
-
-        Client(String id) {
-            this.id = id;
-        }
-    }
-
-    public static final MainThreadInitializedObject<PredictionUiStateManager> INSTANCE =
-            new MainThreadInitializedObject<>(PredictionUiStateManager::new);
-
-    private final Context mContext;
-
-    private final DynamicItemCache mDynamicItemCache;
-    private List mPredictionServicePredictions = Collections.emptyList();
-
-    private int mMaxIconsPerRow;
-
-    private AllAppsContainerView mAppsView;
-
-    private PredictionState mPendingState;
-    private PredictionState mCurrentState;
-
-    private boolean mGettingValidPredictionResults;
-
-    private PredictionUiStateManager(Context context) {
-        mContext = context;
-
-        mDynamicItemCache = new DynamicItemCache(context, this::onAppsUpdated);
-
-        InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
-        mMaxIconsPerRow = idp.numColumns;
-
-        idp.addOnChangeListener(this);
-        mGettingValidPredictionResults = Utilities.getDevicePrefs(context)
-                .getBoolean(LAST_PREDICTION_ENABLED_STATE, true);
-
-        // Call this last
-        mCurrentState = parseLastState();
-    }
-
-    @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        mMaxIconsPerRow = profile.numColumns;
-    }
-
-    public void setTargetAppsView(AllAppsContainerView appsView) {
-        if (mAppsView != null) {
-            mAppsView.getAppsStore().removeUpdateListener(this);
-        }
-        mAppsView = appsView;
-        if (mAppsView != null) {
-            mAppsView.getAppsStore().addUpdateListener(this);
-        }
-        if (mPendingState != null) {
-            applyState(mPendingState);
-            mPendingState = null;
-        } else {
-            applyState(mCurrentState);
-        }
-        updateDependencies(mCurrentState);
-    }
-
-    @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) { }
-
-    @Override
-    public void onStateTransitionComplete(LauncherState state) {
-        if (mAppsView == null) {
-            return;
-        }
-        if (mPendingState != null && canApplyPredictions(mPendingState)) {
-            applyState(mPendingState);
-            mPendingState = null;
-        }
-        if (mPendingState == null) {
-            Launcher.getLauncher(mAppsView.getContext()).getStateManager()
-                    .removeStateListener(this);
-        }
-    }
-
-    private void scheduleApplyPredictedApps(PredictionState state) {
-        boolean registerListener = mPendingState == null;
-        mPendingState = state;
-        if (registerListener) {
-            // Add a listener and wait until appsView is invisible again.
-            Launcher.getLauncher(mAppsView.getContext()).getStateManager().addStateListener(this);
-        }
-    }
-
-    private void applyState(PredictionState state) {
-        mCurrentState = state;
-        if (mAppsView != null) {
-            mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
-                    .setPredictedApps(mCurrentState.apps);
-        }
-    }
-
-    private void updatePredictionStateAfterCallback() {
-        boolean validResults = mPredictionServicePredictions != null
-                && !mPredictionServicePredictions.isEmpty();
-        if (validResults != mGettingValidPredictionResults) {
-            mGettingValidPredictionResults = validResults;
-            Utilities.getDevicePrefs(mContext).edit()
-                    .putBoolean(LAST_PREDICTION_ENABLED_STATE, true)
-                    .apply();
-        }
-        dispatchOnChange(true);
-    }
-
-    public AppPredictor.Callback appPredictorCallback(Client client) {
-        return targets -> {
-            mPredictionServicePredictions = targets;
-            updatePredictionStateAfterCallback();
-        };
-    }
-
-    private void dispatchOnChange(boolean changed) {
-        PredictionState newState = changed
-                ? parseLastState()
-                : mPendingState != null && canApplyPredictions(mPendingState)
-                        ? mPendingState
-                        : mCurrentState;
-        if (changed && mAppsView != null && !canApplyPredictions(newState)) {
-            scheduleApplyPredictedApps(newState);
-        } else {
-            applyState(newState);
-        }
-    }
-
-    private PredictionState parseLastState() {
-        PredictionState state = new PredictionState();
-        state.isEnabled = mGettingValidPredictionResults;
-        if (!state.isEnabled) {
-            state.apps = Collections.EMPTY_LIST;
-            return state;
-        }
-
-        state.apps = new ArrayList<>();
-
-        List<AppTarget> appTargets = mPredictionServicePredictions;
-        if (!appTargets.isEmpty()) {
-            for (AppTarget appTarget : appTargets) {
-                ComponentKey key;
-                if (appTarget.getShortcutInfo() != null) {
-                    key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
-                } else {
-                    key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
-                            appTarget.getClassName()), appTarget.getUser());
-                }
-                state.apps.add(new ComponentKeyMapper(key, mDynamicItemCache));
-            }
-        }
-        updateDependencies(state);
-        return state;
-    }
-
-    private void updateDependencies(PredictionState state) {
-        if (!state.isEnabled || mAppsView == null) {
-            return;
-        }
-        mDynamicItemCache.updateDependencies(state.apps, mAppsView.getAppsStore(), this,
-                mMaxIconsPerRow);
-    }
-
-    @Override
-    public void onAppsUpdated() {
-        dispatchOnChange(false);
-    }
-
-    private boolean canApplyPredictions(PredictionState newState) {
-        if (mAppsView == null) {
-            // If there is no apps view, no need to schedule.
-            return true;
-        }
-        Launcher launcher = Launcher.getLauncher(mAppsView.getContext());
-        PredictionRowView predictionRow = mAppsView.getFloatingHeaderView().
-                findFixedRowByType(PredictionRowView.class);
-        if (!predictionRow.isShown() || predictionRow.getAlpha() == 0 ||
-                launcher.isForceInvisible()) {
-            return true;
-        }
-
-        if (mCurrentState.isEnabled != newState.isEnabled
-                || mCurrentState.apps.isEmpty() != newState.apps.isEmpty()) {
-            // If the visibility of the prediction row is changing, apply immediately.
-            return true;
-        }
-
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            // If we are here & mAppsView.isShown() = true, we are probably in all-apps or mid way
-            return false;
-        }
-        if (!launcher.isInState(OVERVIEW) && !launcher.isInState(BACKGROUND_APP)) {
-            // Just a fallback as we dont need to apply instantly, if we are not in the swipe-up UI
-            return false;
-        }
-
-        // Instead of checking against 1, we should check against (1 + delta), where delta accounts
-        // for the nav-bar height (as app icon can still be visible under the nav-bar). Checking
-        // against 1, keeps the logic simple :)
-        return launcher.getAllAppsController().getProgress() > 1;
-    }
-
-    public PredictionState getCurrentState() {
-        return mCurrentState;
-    }
-
-    /**
-     * Returns ranking info for the app within all apps prediction.
-     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
-     */
-    public OptionalInt getAllAppsRank(@Nullable ItemInfo itemInfo) {
-        if (itemInfo == null || itemInfo.getTargetComponent() == null || itemInfo.user == null) {
-            return OptionalInt.empty();
-        }
-
-        if (itemInfo.itemType == ITEM_TYPE_APPLICATION
-                || itemInfo.itemType == ITEM_TYPE_SHORTCUT
-                || itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
-            ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(),
-                    itemInfo.user);
-            final List<ComponentKeyMapper> apps = getCurrentState().apps;
-            return IntStream.range(0, apps.size())
-                    .filter(index -> key.equals(apps.get(index).getComponentKey()))
-                    .findFirst();
-        }
-
-        return OptionalInt.empty();
-    }
-
-    /**
-     * Fill in predicted_rank field based on app prediction.
-     * Only applicable when {@link ItemInfo#itemType} is one of the followings:
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_APPLICATION},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_SHORTCUT},
-     * {@link LauncherSettings.Favorites#ITEM_TYPE_DEEP_SHORTCUT}
-     */
-    public static void fillInPredictedRank(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
-
-        final PredictionUiStateManager manager = PredictionUiStateManager.INSTANCE.getNoCreate();
-        if (manager == null || itemInfo.getTargetComponent() == null || itemInfo.user == null
-                || (itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
-                && itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
-            return;
-        }
-        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
-            HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
-            return;
-        }
-
-        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-        final List<ComponentKeyMapper> predictedApps = manager.getCurrentState().apps;
-        IntStream.range(0, predictedApps.size())
-                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
-                .findFirst()
-                .ifPresent((rank) -> target.predictedRank = 0 - rank);
-    }
-
-    public static class PredictionState {
-
-        public boolean isEnabled;
-        public List<ComponentKeyMapper> apps;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
deleted file mode 100644
index b94e633..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ /dev/null
@@ -1,723 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.hybridhotseat;
-
-import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.app.prediction.AppPredictionContext;
-import android.app.prediction.AppPredictionManager;
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetEvent;
-import android.content.ComponentName;
-import android.os.Process;
-import android.util.Log;
-import android.view.HapticFeedbackConstants;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Hotseat;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.appprediction.ComponentKeyMapper;
-import com.android.launcher3.appprediction.DynamicItemCache;
-import com.android.launcher3.dragndrop.DragController;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
-import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
-import com.android.launcher3.logging.InstanceId;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.uioverrides.PredictedAppIcon;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.OnboardingPrefs;
-import com.android.launcher3.views.ArrowTipView;
-import com.android.launcher3.views.Snackbar;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.OptionalInt;
-import java.util.stream.IntStream;
-
-/**
- * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
- * pinning of predicted apps and manages replacement of predicted apps with user drag.
- */
-public class HotseatPredictionController implements DragController.DragListener,
-        View.OnAttachStateChangeListener, SystemShortcut.Factory<QuickstepLauncher>,
-        InvariantDeviceProfile.OnIDPChangeListener, AllAppsStore.OnUpdateListener,
-        IconCache.ItemInfoUpdateReceiver, DragSource {
-
-    private static final String TAG = "PredictiveHotseat";
-    private static final boolean DEBUG = false;
-
-    private static final String PREDICTION_CLIENT = "hotseat";
-    private DropTarget.DragObject mDragObject;
-    private int mHotSeatItemsCount;
-    private int mPredictedSpotsCount = 0;
-
-    private Launcher mLauncher;
-    private final Hotseat mHotseat;
-
-    private final HotseatRestoreHelper mRestoreHelper;
-
-    private List<ComponentKeyMapper> mComponentKeyMappers = new ArrayList<>();
-
-    private DynamicItemCache mDynamicItemCache;
-
-    private final HotseatPredictionModel mPredictionModel;
-    private AppPredictor mAppPredictor;
-    private AllAppsStore mAllAppsStore;
-    private AnimatorSet mIconRemoveAnimators;
-    private boolean mUIUpdatePaused = false;
-    private boolean mIsDestroyed = false;
-
-
-    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
-
-    private final View.OnLongClickListener mPredictionLongClickListener = v -> {
-        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
-        if (mLauncher.getWorkspace().isSwitchingState()) return false;
-        if (!mLauncher.getOnboardingPrefs().getBoolean(
-                OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
-            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
-                    R.string.hotseat_prediction_settings, null,
-                    () -> mLauncher.startActivity(getSettingsIntent()));
-            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
-            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-            return true;
-        }
-        // Start the drag
-        mLauncher.getWorkspace().beginDragShared(v, this, new DragOptions());
-        return true;
-    };
-
-    public HotseatPredictionController(Launcher launcher) {
-        mLauncher = launcher;
-        mHotseat = launcher.getHotseat();
-        mAllAppsStore = mLauncher.getAppsView().getAppsStore();
-        LauncherAppState appState = LauncherAppState.getInstance(launcher);
-        mPredictionModel = (HotseatPredictionModel) appState.getPredictionModel();
-        mAllAppsStore.addUpdateListener(this);
-        mDynamicItemCache = new DynamicItemCache(mLauncher, this::fillGapsWithPrediction);
-        mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
-        launcher.getDeviceProfile().inv.addOnChangeListener(this);
-        mHotseat.addOnAttachStateChangeListener(this);
-        mRestoreHelper = new HotseatRestoreHelper(mLauncher);
-        if (mHotseat.isAttachedToWindow()) {
-            onViewAttachedToWindow(mHotseat);
-        }
-    }
-
-    /**
-     * Shows appropriate hotseat education based on prediction enabled and migration states.
-     */
-    public void showEdu() {
-        mLauncher.getStateManager().goToState(NORMAL, true, () -> {
-            if (mComponentKeyMappers.isEmpty()) {
-                // launcher has empty predictions set
-                Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
-                        R.string.hotseat_prediction_settings, null,
-                        () -> mLauncher.startActivity(getSettingsIntent()));
-            } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
-                showDiscoveryTip();
-            } else {
-                HotseatEduController eduController = new HotseatEduController(mLauncher,
-                        mRestoreHelper,
-                        this::createPredictor);
-                eduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
-                eduController.showEdu();
-            }
-        });
-    }
-
-    /**
-     * Shows educational tip for hotseat if user does not go through Tips app.
-     */
-    private void showDiscoveryTip() {
-        if (getPredictedIcons().isEmpty()) {
-            new ArrowTipView(mLauncher).show(
-                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
-        } else {
-            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
-                    R.string.hotseat_prediction_settings, null,
-                    () -> mLauncher.startActivity(getSettingsIntent()));
-        }
-    }
-
-    /**
-     * Returns if hotseat client has predictions
-     */
-    public boolean hasPredictions() {
-        return !mComponentKeyMappers.isEmpty();
-    }
-
-    @Override
-    public void onViewAttachedToWindow(View view) {
-        mLauncher.getDragController().addDragListener(this);
-    }
-
-    @Override
-    public void onViewDetachedFromWindow(View view) {
-        mLauncher.getDragController().removeDragListener(this);
-    }
-
-    private void fillGapsWithPrediction() {
-        fillGapsWithPrediction(false, null);
-    }
-
-    private void fillGapsWithPrediction(boolean animate, Runnable callback) {
-        if (mUIUpdatePaused || mDragObject != null) {
-            return;
-        }
-        List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
-        if (mComponentKeyMappers.isEmpty() != predictedApps.isEmpty()) {
-            // Safely ignore update as AppsList is not ready yet. This will called again once
-            // apps are ready (HotseatPredictionController#onAppsUpdated)
-            return;
-        }
-        int predictionIndex = 0;
-        ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
-        // make sure predicted icon removal and filling predictions don't step on each other
-        if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
-            mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    fillGapsWithPrediction(animate, callback);
-                    mIconRemoveAnimators.removeListener(this);
-                }
-            });
-            return;
-        }
-        for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
-            View child = mHotseat.getChildAt(
-                    mHotseat.getCellXFromOrder(rank),
-                    mHotseat.getCellYFromOrder(rank));
-
-            if (child != null && !isPredictedIcon(child)) {
-                continue;
-            }
-            if (predictedApps.size() <= predictionIndex) {
-                // Remove predicted apps from the past
-                if (isPredictedIcon(child)) {
-                    mHotseat.removeView(child);
-                }
-                continue;
-            }
-            WorkspaceItemInfo predictedItem = predictedApps.get(predictionIndex++);
-            if (isPredictedIcon(child) && child.isEnabled()) {
-                PredictedAppIcon icon = (PredictedAppIcon) child;
-                icon.applyFromWorkspaceItem(predictedItem);
-                icon.finishBinding(mPredictionLongClickListener);
-            } else {
-                newItems.add(predictedItem);
-            }
-            preparePredictionInfo(predictedItem, rank);
-        }
-        mPredictedSpotsCount = predictionIndex;
-        bindItems(newItems, animate, callback);
-    }
-
-    private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
-        AnimatorSet animationSet = new AnimatorSet();
-        for (WorkspaceItemInfo item : itemsToAdd) {
-            PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
-            mLauncher.getWorkspace().addInScreenFromBind(icon, item);
-            icon.finishBinding(mPredictionLongClickListener);
-            if (animate) {
-                animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
-            }
-        }
-        if (animate) {
-            if (callback != null) {
-                animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
-            }
-            animationSet.start();
-        } else {
-            if (callback != null) callback.run();
-        }
-    }
-
-    /**
-     * Unregisters callbacks and frees resources
-     */
-    public void destroy() {
-        mIsDestroyed = true;
-        mAllAppsStore.removeUpdateListener(this);
-        mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
-        mHotseat.removeOnAttachStateChangeListener(this);
-        if (mAppPredictor != null) {
-            mAppPredictor.destroy();
-        }
-    }
-
-    /**
-     * start and pauses predicted apps update on the hotseat
-     */
-    public void setPauseUIUpdate(boolean paused) {
-        mUIUpdatePaused = paused;
-        if (!paused) {
-            fillGapsWithPrediction();
-        }
-    }
-
-    /**
-     * Creates App Predictor with all the current apps pinned on the hotseat
-     */
-    public void createPredictor() {
-        AppPredictionManager apm = mLauncher.getSystemService(AppPredictionManager.class);
-        if (apm == null) {
-            return;
-        }
-        if (mAppPredictor != null) {
-            mAppPredictor.destroy();
-            mAppPredictor = null;
-        }
-        WeakReference<HotseatPredictionController> controllerRef = new WeakReference<>(this);
-
-
-        mPredictionModel.createBundle(bundle -> {
-            if (mIsDestroyed) return;
-            mAppPredictor = apm.createAppPredictionSession(
-                    new AppPredictionContext.Builder(mLauncher)
-                            .setUiSurface(PREDICTION_CLIENT)
-                            .setPredictedTargetCount(mHotSeatItemsCount)
-                            .setExtras(bundle)
-                            .build());
-            mAppPredictor.registerPredictionUpdates(
-                    mLauncher.getApplicationContext().getMainExecutor(),
-                    list -> {
-                        if (controllerRef.get() != null) {
-                            controllerRef.get().setPredictedApps(list);
-                        }
-                    });
-            mAppPredictor.requestPredictionUpdate();
-        });
-        setPauseUIUpdate(false);
-    }
-
-    /**
-     * Create WorkspaceItemInfo objects and binds PredictedAppIcon views for cached predicted items.
-     */
-    public void showCachedItems(List<AppInfo> apps, IntArray ranks) {
-        if (hasPredictions() && mAppPredictor != null) {
-            mAppPredictor.requestPredictionUpdate();
-            fillGapsWithPrediction();
-            return;
-        }
-        int count = Math.min(ranks.size(), apps.size());
-        List<WorkspaceItemInfo> items = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            WorkspaceItemInfo item = new WorkspaceItemInfo(apps.get(i));
-            ComponentKey componentKey = new ComponentKey(item.getTargetComponent(), item.user);
-            preparePredictionInfo(item, ranks.get(i));
-            items.add(item);
-
-            mComponentKeyMappers.add(new ComponentKeyMapper(componentKey, mDynamicItemCache));
-        }
-        updateDependencies();
-        bindItems(items, false, null);
-    }
-
-    private void setPredictedApps(List<AppTarget> appTargets) {
-        mComponentKeyMappers.clear();
-        if (appTargets.isEmpty()) {
-            mRestoreHelper.restoreBackup();
-        }
-        StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
-        ArrayList<ComponentKey> componentKeys = new ArrayList<>();
-        for (AppTarget appTarget : appTargets) {
-            ComponentKey key;
-            if (appTarget.getShortcutInfo() != null) {
-                key = ShortcutKey.fromInfo(appTarget.getShortcutInfo());
-            } else {
-                key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
-                        appTarget.getClassName()), appTarget.getUser());
-            }
-            componentKeys.add(key);
-            predictionLog.append(key.toString());
-            predictionLog.append(",rank:");
-            predictionLog.append(appTarget.getRank());
-            predictionLog.append("\n");
-            mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
-        }
-        predictionLog.append("]");
-        if (Utilities.IS_DEBUG_DEVICE) {
-            HotseatFileLog.INSTANCE.get(mLauncher).log(TAG, predictionLog.toString());
-        }
-        updateDependencies();
-        fillGapsWithPrediction();
-        mPredictionModel.cachePredictionComponentKeys(componentKeys);
-    }
-
-    private void updateDependencies() {
-        mDynamicItemCache.updateDependencies(mComponentKeyMappers, mAllAppsStore, this,
-                mHotSeatItemsCount);
-    }
-
-    /**
-     * Pins a predicted app icon into place.
-     */
-    public void pinPrediction(ItemInfo info) {
-        PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
-                mHotseat.getCellXFromOrder(info.rank),
-                mHotseat.getCellYFromOrder(info.rank));
-        if (icon == null) {
-            return;
-        }
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
-        mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
-                LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
-                workspaceItemInfo.cellX, workspaceItemInfo.cellY);
-        ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
-        icon.pin(workspaceItemInfo);
-        AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(workspaceItemInfo);
-        if (appTarget != null) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                    AppTargetEvent.ACTION_PIN, workspaceItemInfo));
-        }
-    }
-
-    private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
-            List<ComponentKeyMapper> components) {
-        AllAppsStore allAppsStore = mLauncher.getAppsView().getAppsStore();
-        if (allAppsStore.getApps().length == 0) {
-            return Collections.emptyList();
-        }
-
-        List<WorkspaceItemInfo> predictedApps = new ArrayList<>();
-        for (ComponentKeyMapper mapper : components) {
-            ItemInfoWithIcon info = mapper.getApp(allAppsStore);
-            if (info instanceof AppInfo) {
-                WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((AppInfo) info);
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else if (info instanceof WorkspaceItemInfo) {
-                WorkspaceItemInfo predictedApp = new WorkspaceItemInfo((WorkspaceItemInfo) info);
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else {
-                if (DEBUG) {
-                    Log.e(TAG, "Predicted app not found: " + mapper);
-                }
-            }
-            // Stop at the number of hotseat items
-            if (predictedApps.size() == mHotSeatItemsCount) {
-                break;
-            }
-        }
-        return predictedApps;
-    }
-
-    private List<PredictedAppIcon> getPredictedIcons() {
-        List<PredictedAppIcon> icons = new ArrayList<>();
-        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
-        for (int i = 0; i < vg.getChildCount(); i++) {
-            View child = vg.getChildAt(i);
-            if (isPredictedIcon(child)) {
-                icons.add((PredictedAppIcon) child);
-            }
-        }
-        return icons;
-    }
-
-    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
-            ItemInfo draggedInfo) {
-        if (mIconRemoveAnimators != null) {
-            mIconRemoveAnimators.end();
-        }
-        mIconRemoveAnimators = new AnimatorSet();
-        removeOutlineDrawings();
-        for (PredictedAppIcon icon : getPredictedIcons()) {
-            if (!icon.isEnabled()) {
-                continue;
-            }
-            if (icon.getTag().equals(draggedInfo)) {
-                mHotseat.removeView(icon);
-                continue;
-            }
-            int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
-            outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
-                    mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
-            icon.setEnabled(false);
-            ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
-            animator.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (icon.getParent() != null) {
-                        mHotseat.removeView(icon);
-                    }
-                }
-            });
-            mIconRemoveAnimators.play(animator);
-        }
-        mIconRemoveAnimators.start();
-    }
-
-    private void notifyItemAction(AppTargetEvent event) {
-        if (mAppPredictor != null) {
-            mAppPredictor.notifyAppTargetEvent(event);
-        }
-    }
-
-    @Override
-    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
-        removePredictedApps(mOutlineDrawings, dragObject.dragInfo);
-        mDragObject = dragObject;
-        if (mOutlineDrawings.isEmpty()) return;
-        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
-            mHotseat.addDelegatedCellDrawing(outlineDrawing);
-        }
-        mHotseat.invalidate();
-    }
-
-    /**
-     * Unpins pinned app when it's converted into a folder
-     */
-    public void folderCreatedFromWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
-        AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
-        AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
-        if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
-                    AppTargetEvent.ACTION_PIN, folderInfo));
-        }
-        // using folder info with isTrackedForPrediction as itemInfo.container is already changed
-        // to folder by this point
-        if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
-                    AppTargetEvent.ACTION_UNPIN, folderInfo
-            ));
-        }
-    }
-
-    /**
-     * Pins workspace item created when all folder items are removed but one
-     */
-    public void folderConvertedToWorkspaceItem(ItemInfo itemInfo, FolderInfo folderInfo) {
-        AppTarget folderTarget = mPredictionModel.getAppTargetFromInfo(folderInfo);
-        AppTarget itemTarget = mPredictionModel.getAppTargetFromInfo(itemInfo);
-        if (folderTarget != null && HotseatPredictionModel.isTrackedForPrediction(folderInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(folderTarget,
-                    AppTargetEvent.ACTION_UNPIN, folderInfo));
-        }
-        if (itemTarget != null && HotseatPredictionModel.isTrackedForPrediction(itemInfo)) {
-            notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(itemTarget,
-                    AppTargetEvent.ACTION_PIN, itemInfo));
-        }
-    }
-
-    @Override
-    public void onDragEnd() {
-        if (mDragObject == null) {
-            return;
-        }
-
-        ItemInfo dragInfo = mDragObject.dragInfo;
-        if (mDragObject.isMoved()) {
-            AppTarget appTarget = mPredictionModel.getAppTargetFromInfo(dragInfo);
-            //always send pin event first to prevent AiAi from predicting an item moved within
-            // the same page
-            if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(dragInfo)) {
-                notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                        AppTargetEvent.ACTION_PIN, dragInfo));
-            }
-            if (appTarget != null && HotseatPredictionModel.isTrackedForPrediction(
-                    mDragObject.originalDragInfo)) {
-                notifyItemAction(mPredictionModel.wrapAppTargetWithLocation(appTarget,
-                        AppTargetEvent.ACTION_UNPIN, mDragObject.originalDragInfo));
-            }
-        }
-        mDragObject = null;
-        fillGapsWithPrediction(true, this::removeOutlineDrawings);
-    }
-
-
-    @Nullable
-    @Override
-    public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
-            ItemInfo itemInfo) {
-        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            return null;
-        }
-        return new PinPrediction(activity, itemInfo);
-    }
-
-    private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
-        itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-        itemInfo.rank = rank;
-        itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
-        itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
-        itemInfo.screenId = rank;
-    }
-
-    private void removeOutlineDrawings() {
-        if (mOutlineDrawings.isEmpty()) return;
-        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
-            mHotseat.removeDelegatedCellDrawing(outlineDrawing);
-        }
-        mHotseat.invalidate();
-        mOutlineDrawings.clear();
-    }
-
-    @Override
-    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
-        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
-            this.mHotSeatItemsCount = profile.numHotseatIcons;
-            createPredictor();
-        }
-    }
-
-    @Override
-    public void onAppsUpdated() {
-        fillGapsWithPrediction();
-    }
-
-    @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) {
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
-        //Does nothing
-    }
-
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> parents) {
-        mHotseat.fillInLogContainerData(childInfo, child, parents);
-    }
-
-    /**
-     * Logs rank info based on current list of predicted items
-     */
-    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
-        if (Utilities.IS_DEBUG_DEVICE) {
-            final String pkg = itemInfo.getTargetComponent() != null
-                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
-            HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
-                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
-                            && !Process.myUserHandle().equals(itemInfo.user))
-                            + ",launchLocation:" + itemInfo.container);
-        }
-
-        if (itemInfo.getTargetComponent() == null || itemInfo.user == null) {
-            return;
-        }
-
-        final ComponentKey key = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-
-        final List<ComponentKeyMapper> predictedApps = new ArrayList<>(mComponentKeyMappers);
-        OptionalInt rank = IntStream.range(0, predictedApps.size())
-                .filter(index -> key.equals(predictedApps.get(index).getComponentKey()))
-                .findFirst();
-        if (!rank.isPresent()) {
-            return;
-        }
-
-        int cardinality = 0;
-        for (PredictedAppIcon icon : getPredictedIcons()) {
-            ItemInfo info = (ItemInfo) icon.getTag();
-            cardinality |= 1 << info.screenId;
-        }
-
-        PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
-        containerBuilder.setCardinality(cardinality);
-        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            containerBuilder.setIndex(rank.getAsInt());
-        }
-        mLauncher.getStatsLogManager().logger()
-                .withInstanceId(instanceId)
-                .withRank(rank.getAsInt())
-                .withContainerInfo(ContainerInfo.newBuilder()
-                        .setPredictedHotseatContainer(containerBuilder)
-                        .build())
-                .log(LAUNCHER_HOTSEAT_RANKED);
-    }
-
-    private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
-
-        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
-            super(R.drawable.ic_pin, R.string.pin_prediction, target,
-                    itemInfo);
-        }
-
-        @Override
-        public void onClick(View view) {
-            dismissTaskMenuView(mTarget);
-            pinPrediction(mItemInfo);
-        }
-    }
-
-    /**
-     * Fill in predicted_rank field based on app prediction.
-     * Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
-     */
-    public static void encodeHotseatLayoutIntoPredictionRank(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
-        QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
-        if (launcher == null || launcher.getHotseatPredictionController() == null
-                || itemInfo.getTargetComponent() == null) {
-            return;
-        }
-        HotseatPredictionController controller = launcher.getHotseatPredictionController();
-
-        final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
-
-        final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
-        OptionalInt rank = IntStream.range(0, predictedApps.size())
-                .filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
-                .findFirst();
-
-        target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
-                + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
-    }
-
-    private static boolean isPredictedIcon(View view) {
-        return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
-                && ((WorkspaceItemInfo) view.getTag()).container
-                == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
deleted file mode 100644
index fc9a11b..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides.states;
-
-import com.android.launcher3.Launcher;
-
-public class OverviewPeekState extends OverviewState {
-    private static final float OVERVIEW_OFFSET = 0.7f;
-
-    public OverviewPeekState(int id) {
-        super(id);
-    }
-
-    @Override
-    public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {NO_SCALE, OVERVIEW_OFFSET};
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
deleted file mode 100644
index fac478e..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ /dev/null
@@ -1,279 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Touch controller which handles swipe and hold to go to Overview
- */
-public class FlingAndHoldTouchController extends PortraitStatesTouchController {
-
-    private static final long PEEK_IN_ANIM_DURATION = 240;
-    private static final long PEEK_OUT_ANIM_DURATION = 100;
-    private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
-
-    protected final MotionPauseDetector mMotionPauseDetector;
-    private final float mMotionPauseMinDisplacement;
-    private final float mMotionPauseMaxDisplacement;
-
-    private AnimatorSet mPeekAnim;
-
-    public FlingAndHoldTouchController(Launcher l) {
-        super(l, false /* allowDragToOverview */);
-        mMotionPauseDetector = new MotionPauseDetector(l);
-        mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
-        mMotionPauseMaxDisplacement = getMotionPauseMaxDisplacement();
-    }
-
-    protected float getMotionPauseMaxDisplacement() {
-        return getShiftRange() * MAX_DISPLACEMENT_PERCENT;
-    }
-
-    @Override
-    protected long getAtomicDuration() {
-        return QuickstepAtomicAnimationFactory.ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
-    }
-
-    @Override
-    public void onDragStart(boolean start, float startDisplacement) {
-        mMotionPauseDetector.clear();
-
-        super.onDragStart(start, startDisplacement);
-
-        if (handlingOverviewAnim()) {
-            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
-        }
-
-        if (mAtomicAnim != null) {
-            mAtomicAnim.cancel();
-        }
-    }
-
-    protected void onMotionPauseChanged(boolean isPaused) {
-        RecentsView recentsView = mLauncher.getOverviewPanel();
-        recentsView.setOverviewStateEnabled(isPaused);
-        if (mPeekAnim != null) {
-            mPeekAnim.cancel();
-        }
-        LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
-        LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
-        long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
-
-        StateAnimationConfig config = new StateAnimationConfig();
-        config.duration = peekDuration;
-        config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-        mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
-                fromState, toState, config);
-        mPeekAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mPeekAnim = null;
-            }
-        });
-        mPeekAnim.start();
-        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
-        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
-                .setDuration(peekDuration).start();
-    }
-
-    /**
-     * @return Whether we are handling the overview animation, rather than
-     * having it as part of the existing animation to the target state.
-     */
-    protected boolean handlingOverviewAnim() {
-        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
-        return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
-    }
-
-    @Override
-    protected StateAnimationConfig getConfigForStates(
-            LauncherState fromState, LauncherState toState) {
-        if (fromState == NORMAL && toState == ALL_APPS) {
-            StateAnimationConfig builder = new StateAnimationConfig();
-            // Fade in prediction icons quickly, then rest of all apps after reaching overview.
-            float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
-                    - OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
-                    ACCEL,
-                    0,
-                    ALL_APPS_CONTENT_FADE_THRESHOLD));
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
-                    ACCEL,
-                    progressToReachOverview,
-                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
-
-            // Get workspace out of the way quickly, to prepare for potential pause.
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
-            builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
-            return builder;
-        } else if (fromState == ALL_APPS && toState == NORMAL) {
-            StateAnimationConfig builder = new StateAnimationConfig();
-            // Keep all apps/predictions opaque until the very end of the transition.
-            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
-            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
-                    DEACCEL,
-                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
-                    progressToReachOverview));
-            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
-                    DEACCEL,
-                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
-                    1));
-            return builder;
-        }
-        return super.getConfigForStates(fromState, toState);
-    }
-
-    @Override
-    public boolean onDrag(float displacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "FlingAndHoldTouchController");
-        }
-        float upDisplacement = -displacement;
-        mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
-                || upDisplacement < mMotionPauseMinDisplacement
-                || upDisplacement > mMotionPauseMaxDisplacement);
-        mMotionPauseDetector.addPosition(event);
-        return super.onDrag(displacement, event);
-    }
-
-    @Override
-    public void onDragEnd(float velocity) {
-        if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
-            goToOverviewOnDragEnd(velocity);
-        } else {
-            super.onDragEnd(velocity);
-        }
-
-        View searchView = mLauncher.getAppsView().getSearchView();
-        if (searchView instanceof FeedbackHandler) {
-            ((FeedbackHandler) searchView).resetFeedback();
-        }
-        mMotionPauseDetector.clear();
-    }
-
-    protected void goToOverviewOnDragEnd(float velocity) {
-        if (mPeekAnim != null) {
-            mPeekAnim.cancel();
-        }
-
-        Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
-                .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
-        mAtomicAnim = new AnimatorSet();
-        mAtomicAnim.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                super.onAnimationEnd(animation);
-                if (mCancelled) {
-                    StateAnimationConfig config = new StateAnimationConfig();
-                    config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-                    config.duration = PEEK_OUT_ANIM_DURATION;
-                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
-                            mFromState, mToState, config);
-                    mPeekAnim.start();
-                }
-                mAtomicAnim = null;
-            }
-        });
-        mAtomicAnim.play(overviewAnim);
-        mAtomicAnim.start();
-    }
-
-    @Override
-    protected void goToTargetState(LauncherState targetState, int logAction) {
-        if (mPeekAnim != null && mPeekAnim.isStarted()) {
-            // Don't jump to the target state until overview is no longer peeking.
-            mPeekAnim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    FlingAndHoldTouchController.super.goToTargetState(targetState, logAction);
-                }
-            });
-        } else {
-            super.goToTargetState(targetState, logAction);
-        }
-    }
-
-    @Override
-    @AnimationFlags
-    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
-        if (handlingOverviewAnim()) {
-            // We don't want the state transition to all apps to animate overview,
-            // as that will cause a jump after our atomic animation.
-            return animComponents | SKIP_OVERVIEW;
-        } else {
-            return animComponents;
-        }
-    }
-
-    /**
-     * Interface for views with feedback animation requiring reset
-     */
-    public interface FeedbackHandler {
-
-        /**
-         * reset searchWidget feedback
-         */
-        void resetFeedback();
-    }
-
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
deleted file mode 100644
index 9316938..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ /dev/null
@@ -1,265 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides.touchcontrollers;
-
-import static com.android.launcher3.LauncherState.HINT_STATE;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.graphics.PointF;
-import android.util.Log;
-import android.view.MotionEvent;
-
-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.graphics.OverviewScrim;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.quickstep.util.StaggeredWorkspaceAnim;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
- * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
- * first home screen instead of to Overview.
- */
-public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {
-
-
-    // How much of the movement to use for translating overview after swipe and hold.
-    private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
-    private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
-    private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
-
-    private final RecentsView mRecentsView;
-
-    private boolean mDidTouchStartInNavBar;
-    private boolean mReachedOverview;
-    private boolean mIsOverviewRehidden;
-    private boolean mIsHomeStaggeredAnimFinished;
-    // The last recorded displacement before we reached overview.
-    private PointF mStartDisplacement = new PointF();
-
-    // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
-    private ObjectAnimator mNormalToHintOverviewScrimAnimator;
-
-    public NoButtonNavbarToOverviewTouchController(Launcher l) {
-        super(l);
-        mRecentsView = l.getOverviewPanel();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
-        }
-    }
-
-    @Override
-    protected float getMotionPauseMaxDisplacement() {
-        // No need to disallow pause when swiping up all the way up the screen (unlike
-        // FlingAndHoldTouchController where user is probably intending to go to all apps).
-        return Float.MAX_VALUE;
-    }
-
-    @Override
-    protected boolean canInterceptTouch(MotionEvent ev) {
-        mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
-        return super.canInterceptTouch(ev);
-    }
-
-    @Override
-    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
-        if (fromState == NORMAL && mDidTouchStartInNavBar) {
-            return HINT_STATE;
-        } else if (fromState == OVERVIEW && isDragTowardPositive) {
-            // Don't allow swiping up to all apps.
-            return OVERVIEW;
-        }
-        return super.getTargetState(fromState, isDragTowardPositive);
-    }
-
-    @Override
-    protected float initCurrentAnimation(int animComponents) {
-        float progressMultiplier = super.initCurrentAnimation(animComponents);
-        if (mToState == HINT_STATE) {
-            // Track the drag across the entire height of the screen.
-            progressMultiplier = -1 / getShiftRange();
-        }
-        return progressMultiplier;
-    }
-
-    @Override
-    public void onDragStart(boolean start, float startDisplacement) {
-        super.onDragStart(start, startDisplacement);
-        if (mFromState == NORMAL && mToState == HINT_STATE) {
-            mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
-                    mLauncher.getDragLayer().getOverviewScrim(),
-                    OverviewScrim.SCRIM_PROGRESS,
-                    mFromState.getOverviewScrimAlpha(mLauncher),
-                    mToState.getOverviewScrimAlpha(mLauncher));
-        }
-        mReachedOverview = false;
-    }
-
-    @Override
-    protected void updateProgress(float fraction) {
-        super.updateProgress(fraction);
-        if (mNormalToHintOverviewScrimAnimator != null) {
-            mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
-        }
-    }
-
-    @Override
-    public void onDragEnd(float velocity) {
-        super.onDragEnd(velocity);
-        mNormalToHintOverviewScrimAnimator = null;
-    }
-
-    @Override
-    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
-            LauncherState targetState, float velocity, boolean isFling) {
-        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
-                isFling);
-        if (targetState == HINT_STATE) {
-            // Normally we compute the duration based on the velocity and distance to the given
-            // state, but since the hint state tracks the entire screen without a clear endpoint, we
-            // need to manually set the duration to a reasonable value.
-            animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
-        }
-    }
-
-    @Override
-    protected void onMotionPauseChanged(boolean isPaused) {
-        if (mCurrentAnimation == null) {
-            return;
-        }
-        mNormalToHintOverviewScrimAnimator = null;
-        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
-            mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
-                mReachedOverview = true;
-                maybeSwipeInteractionToOverviewComplete();
-            });
-        });
-        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-    }
-
-    private void maybeSwipeInteractionToOverviewComplete() {
-        if (mReachedOverview && mDetector.isSettlingState()) {
-            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
-        }
-    }
-
-    // Used if flinging back to home after reaching overview
-    private void maybeSwipeInteractionToHomeComplete() {
-        if (mIsHomeStaggeredAnimFinished && mIsOverviewRehidden) {
-            onSwipeInteractionCompleted(NORMAL, Touch.FLING);
-        }
-    }
-
-    @Override
-    protected boolean handlingOverviewAnim() {
-        return mDidTouchStartInNavBar && super.handlingOverviewAnim();
-    }
-
-    @Override
-    public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
-        }
-        if (mMotionPauseDetector.isPaused()) {
-            if (!mReachedOverview) {
-                mStartDisplacement.set(xDisplacement, yDisplacement);
-            } else {
-                mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
-                        * OVERVIEW_MOVEMENT_FACTOR);
-                mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
-                        * OVERVIEW_MOVEMENT_FACTOR);
-            }
-            // Stay in Overview.
-            return true;
-        }
-        return super.onDrag(yDisplacement, xDisplacement, event);
-    }
-
-    @Override
-    protected void goToOverviewOnDragEnd(float velocity) {
-        float velocityDp = dpiFromPx(velocity);
-        boolean isFling = Math.abs(velocityDp) > 1;
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
-        boolean goToHomeInsteadOfOverview = isFling;
-        if (goToHomeInsteadOfOverview) {
-            if (velocity > 0) {
-                stateManager.goToState(NORMAL, true,
-                        () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
-            } else {
-                mIsHomeStaggeredAnimFinished = mIsOverviewRehidden = false;
-
-                StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
-                        mLauncher, velocity, false /* animateOverviewScrim */);
-                staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
-                    @Override
-                    public void onAnimationSuccess(Animator animator) {
-                        mIsHomeStaggeredAnimFinished = true;
-                        maybeSwipeInteractionToHomeComplete();
-                    }
-                }).start();
-
-                // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
-                stateManager.cancelAnimation();
-                StateAnimationConfig config = new StateAnimationConfig();
-                config.duration = OVERVIEW.getTransitionDuration(mLauncher);
-                config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
-                AnimatorSet anim = stateManager.createAtomicAnimation(
-                        stateManager.getState(), NORMAL, config);
-                anim.addListener(AnimationSuccessListener.forRunnable(() -> {
-                    mIsOverviewRehidden = true;
-                    maybeSwipeInteractionToHomeComplete();
-                }));
-                anim.start();
-            }
-        }
-        if (mReachedOverview) {
-            float distanceDp = dpiFromPx(Math.max(
-                    Math.abs(mRecentsView.getTranslationX()),
-                    Math.abs(mRecentsView.getTranslationY())));
-            long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
-                    distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
-            mRecentsView.animate()
-                    .translationX(0)
-                    .translationY(0)
-                    .setInterpolator(ACCEL_DEACCEL)
-                    .setDuration(duration)
-                    .withEndAction(goToHomeInsteadOfOverview
-                            ? null
-                            : this::maybeSwipeInteractionToOverviewComplete);
-        }
-    }
-
-    private float dpiFromPx(float pixels) {
-        return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
deleted file mode 100644
index b49299d..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ /dev/null
@@ -1,393 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.widget.Toast.LENGTH_SHORT;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.Build;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.widget.Toast;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.UiThread;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.util.WindowBounds;
-import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener;
-import com.android.quickstep.util.ActiveGestureLog;
-import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SurfaceTransactionApplier;
-import com.android.quickstep.util.TransformParams;
-import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-
-import java.util.ArrayList;
-import java.util.function.Consumer;
-
-/**
- * Base class for swipe up handler with some utility methods
- */
-@TargetApi(Build.VERSION_CODES.Q)
-public abstract class BaseSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
-        extends SwipeUpAnimationLogic implements RecentsAnimationListener {
-
-    private static final String TAG = "BaseSwipeUpHandler";
-
-    protected final BaseActivityInterface<?, T> mActivityInterface;
-    protected final InputConsumerController mInputConsumer;
-
-    protected final ActivityInitListener mActivityInitListener;
-
-    protected RecentsAnimationController mRecentsAnimationController;
-    protected RecentsAnimationTargets mRecentsAnimationTargets;
-
-    // Callbacks to be made once the recents animation starts
-    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
-
-    protected T mActivity;
-    protected Q mRecentsView;
-
-    protected Runnable mGestureEndCallback;
-
-    protected MultiStateCallback mStateCallback;
-
-    protected boolean mCanceled;
-
-    private boolean mRecentsViewScrollLinked = false;
-
-    protected BaseSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, new TransformParams());
-        mActivityInterface = gestureState.getActivityInterface();
-        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
-        mInputConsumer = inputConsumer;
-    }
-
-    /**
-     * To be called at the end of constructor of subclasses. This calls various methods which can
-     * depend on proper class initialization.
-     */
-    protected void initAfterSubclassConstructor() {
-        initTransitionEndpoints(
-                mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
-    }
-
-    protected void performHapticFeedback() {
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
-    }
-
-    public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
-        return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
-    }
-
-    public void setGestureEndCallback(Runnable gestureEndCallback) {
-        mGestureEndCallback = gestureEndCallback;
-    }
-
-    public abstract Intent getLaunchIntent();
-
-    protected void linkRecentsViewScroll() {
-        SurfaceTransactionApplier.create(mRecentsView, applier -> {
-            mTransformParams.setSyncTransactionApplier(applier);
-            runOnRecentsAnimationStart(() ->
-                    mRecentsAnimationTargets.addReleaseCheck(applier));
-        });
-
-        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
-            if (moveWindowWithRecentsScroll()) {
-                updateFinalShift();
-            }
-        });
-        runOnRecentsAnimationStart(() ->
-                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
-                        mRecentsAnimationTargets));
-        mRecentsViewScrollLinked = true;
-    }
-
-    protected void startNewTask(Consumer<Boolean> resultCallback) {
-        // Launch the task user scrolled to (mRecentsView.getNextPage()).
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            // We finish recents animation inside launchTask() when live tile is enabled.
-            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
-                    true /* freezeTaskList */);
-        } else {
-            if (!mCanceled) {
-                TaskView nextTask = mRecentsView.getNextPageTaskView();
-                if (nextTask != null) {
-                    int taskId = nextTask.getTask().key.id;
-                    mGestureState.updateLastStartedTaskId(taskId);
-                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
-                            .contains(taskId);
-                    nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
-                            success -> {
-                                resultCallback.accept(success);
-                                if (success) {
-                                    if (hasTaskPreviouslyAppeared) {
-                                        onRestartPreviouslyAppearedTask();
-                                    }
-                                } else {
-                                    mActivityInterface.onLaunchTaskFailed();
-                                    nextTask.notifyTaskLaunchFailed(TAG);
-                                    mRecentsAnimationController.finish(true /* toRecents */, null);
-                                }
-                            }, MAIN_EXECUTOR.getHandler());
-                } else {
-                    mActivityInterface.onLaunchTaskFailed();
-                    Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
-                    mRecentsAnimationController.finish(true /* toRecents */, null);
-                }
-            }
-            mCanceled = false;
-        }
-    }
-
-    /**
-     * Called when we successfully startNewTask() on the task that was previously running. Normally
-     * we call resumeLastTask() when returning to the previously running task, but this handles a
-     * specific edge case: if we switch from A to B, and back to A before B appears, we need to
-     * start A again to ensure it stays on top.
-     */
-    @CallSuper
-    protected void onRestartPreviouslyAppearedTask() {
-        // Finish the controller here, since we won't get onTaskAppeared() for a task that already
-        // appeared.
-        if (mRecentsAnimationController != null) {
-            mRecentsAnimationController.finish(false, null);
-        }
-    }
-
-    /**
-     * Runs the given {@param action} if the recents animation has already started, or queues it to
-     * be run when it is next started.
-     */
-    protected void runOnRecentsAnimationStart(Runnable action) {
-        if (mRecentsAnimationTargets == null) {
-            mRecentsAnimationStartCallbacks.add(action);
-        } else {
-            action.run();
-        }
-    }
-
-    /**
-     * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
-     * @return whether the recents animation has started and there are valid app targets.
-     */
-    protected boolean hasTargets() {
-        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
-    }
-
-    @Override
-    public void onRecentsAnimationStart(RecentsAnimationController recentsAnimationController,
-            RecentsAnimationTargets targets) {
-        mRecentsAnimationController = recentsAnimationController;
-        mRecentsAnimationTargets = targets;
-        mTransformParams.setTargetSet(mRecentsAnimationTargets);
-        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
-                mGestureState.getRunningTaskId());
-
-        if (runningTaskTarget != null) {
-            mTaskViewSimulator.setPreview(runningTaskTarget);
-        }
-
-        // Only initialize the device profile, if it has not been initialized before, as in some
-        // configurations targets.homeContentInsets may not be correct.
-        if (mActivity == null) {
-            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
-            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
-                Rect overviewStackBounds = mActivityInterface
-                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
-                dp = dp.getMultiWindowProfile(mContext,
-                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
-            } else {
-                // If we are not in multi-window mode, home insets should be same as system insets.
-                dp = dp.copy(mContext);
-            }
-            dp.updateInsets(targets.homeContentInsets);
-            dp.updateIsSeascape(mContext);
-            initTransitionEndpoints(dp);
-        }
-
-        // Notify when the animation starts
-        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
-            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
-                action.run();
-            }
-            mRecentsAnimationStartCallbacks.clear();
-        }
-    }
-
-    @Override
-    public void onRecentsAnimationCanceled(ThumbnailData thumbnailData) {
-        mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
-        if (mRecentsView != null) {
-            mRecentsView.setRecentsAnimationTargets(null, null);
-        }
-    }
-
-    @Override
-    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-        mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
-        if (mRecentsView != null) {
-            mRecentsView.setRecentsAnimationTargets(null, null);
-        }
-    }
-
-    @Override
-    public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
-        if (mRecentsAnimationController != null) {
-            if (handleTaskAppeared(appearedTaskTarget)) {
-                mRecentsAnimationController.finish(false /* toRecents */,
-                        null /* onFinishComplete */);
-                mActivityInterface.onLaunchTaskSuccess();
-                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
-            }
-        }
-    }
-
-    /** @return Whether this was the task we were waiting to appear, and thus handled it. */
-    protected abstract boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget);
-
-    /**
-     * @return The index of the TaskView in RecentsView whose taskId matches the task that will
-     * resume if we finish the controller.
-     */
-    protected int getLastAppearedTaskIndex() {
-        return mGestureState.getLastAppearedTaskId() != -1
-                ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
-                : mRecentsView.getRunningTaskIndex();
-    }
-
-    /**
-     * @return Whether we are continuing a gesture that already landed on a new task,
-     * but before that task appeared.
-     */
-    protected boolean hasStartedNewTask() {
-        return mGestureState.getLastStartedTaskId() != -1;
-    }
-
-    /**
-     * Return true if the window should be translated horizontally if the recents view scrolls
-     */
-    protected abstract boolean moveWindowWithRecentsScroll();
-
-    protected boolean onActivityInit(Boolean alreadyOnHome) {
-        T createdActivity = mActivityInterface.getCreatedActivity();
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
-        }
-        if (createdActivity != null) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
-            }
-            initTransitionEndpoints(createdActivity.getDeviceProfile());
-        }
-        return true;
-    }
-
-    /**
-     * Called to create a input proxy for the running task
-     */
-    @UiThread
-    protected abstract InputConsumer createNewInputProxyHandler();
-
-    /**
-     * Called when the value of {@link #mCurrentShift} changes
-     */
-    @UiThread
-    public abstract void updateFinalShift();
-
-    /**
-     * Called when motion pause is detected
-     */
-    public abstract void onMotionPauseChanged(boolean isPaused);
-
-    @UiThread
-    public void onGestureStarted(boolean isLikelyToStartNewTask) { }
-
-    @UiThread
-    public abstract void onGestureCancelled();
-
-    @UiThread
-    public abstract void onGestureEnded(float endVelocity, PointF velocity, PointF downPos);
-
-    public abstract void onConsumerAboutToBeSwitched();
-
-    public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) { }
-
-    /**
-     * Registers a callback to run when the activity is ready.
-     * @param intent The intent that will be used to start the activity if it doesn't exist already.
-     */
-    public void initWhenReady(Intent intent) {
-        // Preload the plan
-        RecentsModel.INSTANCE.get(mContext).getTasks(null);
-
-        mActivityInitListener.register(intent);
-    }
-
-    /**
-     * Applies the transform on the recents animation
-     */
-    protected void applyWindowTransform() {
-        if (mWindowTransitionController != null) {
-            float progress = mCurrentShift.value / mDragLengthFactor;
-            mWindowTransitionController.setPlayFraction(progress);
-        }
-        if (mRecentsAnimationTargets != null) {
-            if (mRecentsViewScrollLinked) {
-                mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
-            }
-            mTaskViewSimulator.apply(mTransformParams);
-        }
-    }
-
-    @Override
-    protected RectFSpringAnim createWindowAnimationToHome(float startProgress,
-            HomeAnimationFactory homeAnimationFactory) {
-        RectFSpringAnim anim =
-                super.createWindowAnimationToHome(startProgress, homeAnimationFactory);
-        if (mRecentsAnimationTargets != null) {
-            mRecentsAnimationTargets.addReleaseCheck(anim);
-        }
-        return anim;
-    }
-
-    public interface Factory {
-
-        BaseSwipeUpHandler newHandler(
-                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
deleted file mode 100644
index e000803..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/logging/UserEventDispatcherAppPredictionExtension.java
+++ /dev/null
@@ -1,49 +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.
- */
-package com.android.quickstep.logging;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-
-import java.util.ArrayList;
-
-/**
- * This class handles AOSP MetricsLogger function calls and logging around
- * quickstep interactions and app launches.
- */
-@SuppressWarnings("unused")
-public class UserEventDispatcherAppPredictionExtension extends UserEventDispatcherExtension {
-
-    public static final int ALL_APPS_PREDICTION_TIPS = 2;
-
-    private static final String TAG = "UserEventDispatcher";
-
-    public UserEventDispatcherAppPredictionExtension(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected void onFillInLogContainerData(
-            @NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target,
-            @NonNull ArrayList<LauncherLogProto.Target> targets) {
-        PredictionUiStateManager.fillInPredictedRank(itemInfo, target);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
deleted file mode 100644
index 190763a..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ProtoTracer.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.util;
-
-import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H;
-import static com.android.launcher3.tracing.nano.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L;
-
-import android.content.Context;
-import android.os.SystemClock;
-
-import com.android.launcher3.tracing.nano.LauncherTraceProto;
-import com.android.launcher3.tracing.nano.LauncherTraceEntryProto;
-import com.android.launcher3.tracing.nano.LauncherTraceFileProto;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.tracing.FrameProtoTracer;
-import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
-import com.android.systemui.shared.tracing.ProtoTraceable;
-import com.google.protobuf.nano.MessageNano;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Queue;
-
-
-/**
- * Controller for coordinating winscope proto tracing.
- */
-public class ProtoTracer implements ProtoTraceParams<MessageNano,
-        LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> {
-
-    public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
-            new MainThreadInitializedObject<>(ProtoTracer::new);
-
-    private static final String TAG = "ProtoTracer";
-    private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
-
-    private final Context mContext;
-    private final FrameProtoTracer<MessageNano,
-            LauncherTraceFileProto, LauncherTraceEntryProto, LauncherTraceProto> mProtoTracer;
-
-    public ProtoTracer(Context context) {
-        mContext = context;
-        mProtoTracer = new FrameProtoTracer<>(this);
-    }
-
-    @Override
-    public File getTraceFile() {
-        return new File(mContext.getFilesDir(), "launcher_trace.pb");
-    }
-
-    @Override
-    public LauncherTraceFileProto getEncapsulatingTraceProto() {
-        return new LauncherTraceFileProto();
-    }
-
-    @Override
-    public LauncherTraceEntryProto updateBufferProto(LauncherTraceEntryProto reuseObj,
-            ArrayList<ProtoTraceable<LauncherTraceProto>> traceables) {
-        LauncherTraceEntryProto proto = reuseObj != null
-                ? reuseObj
-                : new LauncherTraceEntryProto();
-        proto.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
-        proto.launcher = proto.launcher != null ? proto.launcher : new LauncherTraceProto();
-        for (ProtoTraceable t : traceables) {
-            t.writeToProto(proto.launcher);
-        }
-        return proto;
-    }
-
-    @Override
-    public byte[] serializeEncapsulatingProto(LauncherTraceFileProto encapsulatingProto,
-            Queue<LauncherTraceEntryProto> buffer) {
-        encapsulatingProto.magicNumber = MAGIC_NUMBER_VALUE;
-        encapsulatingProto.entry = buffer.toArray(new LauncherTraceEntryProto[0]);
-        return MessageNano.toByteArray(encapsulatingProto);
-    }
-
-    @Override
-    public byte[] getProtoBytes(MessageNano proto) {
-        return MessageNano.toByteArray(proto);
-    }
-
-    @Override
-    public int getProtoSize(MessageNano proto) {
-        return proto.getCachedSize();
-    }
-
-    public void start() {
-        mProtoTracer.start();
-    }
-
-    public void stop() {
-        mProtoTracer.stop();
-    }
-
-    public void add(ProtoTraceable<LauncherTraceProto> traceable) {
-        mProtoTracer.add(traceable);
-    }
-
-    public void remove(ProtoTraceable<LauncherTraceProto> traceable) {
-        mProtoTracer.remove(traceable);
-    }
-
-    public void scheduleFrameUpdate() {
-        mProtoTracer.scheduleFrameUpdate();
-    }
-
-    public void update() {
-        mProtoTracer.update();
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
deleted file mode 100644
index 85006da..0000000
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_SHELF_ANIM;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.view.animation.Interpolator;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.uioverrides.states.OverviewState;
-
-/**
- * Animates the shelf between states HIDE, PEEK, and OVERVIEW.
- */
-public class ShelfPeekAnim {
-
-    public static final Interpolator INTERPOLATOR = OVERSHOOT_1_2;
-    public static final long DURATION = 240;
-
-    private final Launcher mLauncher;
-
-    private ShelfAnimState mShelfState;
-    private boolean mIsPeeking;
-
-    public ShelfPeekAnim(Launcher launcher) {
-        mLauncher = launcher;
-    }
-
-    /**
-     * Animates to the given state, canceling the previous animation if it was still running.
-     */
-    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
-        if (mShelfState == shelfState || FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            return;
-        }
-        mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
-        mShelfState = shelfState;
-        mIsPeeking = mShelfState == ShelfAnimState.PEEK || mShelfState == ShelfAnimState.HIDE;
-        if (mShelfState == ShelfAnimState.CANCEL) {
-            return;
-        }
-        float shelfHiddenProgress = BACKGROUND_APP.getVerticalProgress(mLauncher);
-        float shelfOverviewProgress = OVERVIEW.getVerticalProgress(mLauncher);
-        // Peek based on default overview progress so we can see hotseat if we're showing
-        // that instead of predictions in overview.
-        float defaultOverviewProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
-        float shelfPeekingProgress = shelfHiddenProgress
-                - (shelfHiddenProgress - defaultOverviewProgress) * 0.25f;
-        float toProgress = mShelfState == ShelfAnimState.HIDE
-                ? shelfHiddenProgress
-                : mShelfState == ShelfAnimState.PEEK
-                        ? shelfPeekingProgress
-                        : shelfOverviewProgress;
-        Animator shelfAnim = mLauncher.getStateManager()
-                .createStateElementAnimation(INDEX_SHELF_ANIM, toProgress);
-        shelfAnim.setInterpolator(interpolator);
-        shelfAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mShelfState = ShelfAnimState.CANCEL;
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                mIsPeeking = mShelfState == ShelfAnimState.PEEK;
-            }
-        });
-        shelfAnim.setDuration(duration).start();
-    }
-
-    /** @return Whether the shelf is currently peeking or animating to or from peeking. */
-    public boolean isPeeking() {
-        return mIsPeeking;
-    }
-
-    /** The various shelf states we can animate to. */
-    public enum ShelfAnimState {
-        HIDE(true), PEEK(true), OVERVIEW(false), CANCEL(false);
-
-        ShelfAnimState(boolean shouldPreformHaptic) {
-            this.shouldPreformHaptic = shouldPreformHaptic;
-        }
-
-        public final boolean shouldPreformHaptic;
-    }
-}
diff --git a/quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml b/quickstep/res/drawable/all_apps_edu_circle.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/all_apps_edu_circle.xml
rename to quickstep/res/drawable/all_apps_edu_circle.xml
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml b/quickstep/res/drawable/chip_hint_background_light.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/chip_hint_background_light.xml
rename to quickstep/res/drawable/chip_hint_background_light.xml
diff --git a/quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml b/quickstep/res/drawable/chip_scrim_gradient.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/chip_scrim_gradient.xml
rename to quickstep/res/drawable/chip_scrim_gradient.xml
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/res/drawable/hotseat_edu_notification_icon.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
rename to quickstep/res/drawable/hotseat_edu_notification_icon.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml b/quickstep/res/layout/all_apps_edu_view.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/all_apps_edu_view.xml
rename to quickstep/res/layout/all_apps_edu_view.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml b/quickstep/res/layout/fallback_recents_activity.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/fallback_recents_activity.xml
rename to quickstep/res/layout/fallback_recents_activity.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/floating_header_content.xml b/quickstep/res/layout/floating_header_content.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/floating_header_content.xml
rename to quickstep/res/layout/floating_header_content.xml
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index e05688e..d15a2d2 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -17,9 +17,7 @@
 <com.android.quickstep.views.OverviewActionsView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="@dimen/overview_actions_height"
-    android:layout_gravity="center_horizontal|bottom"
-    android:layout_marginLeft="@dimen/overview_actions_horizontal_margin"
-    android:layout_marginRight="@dimen/overview_actions_horizontal_margin">
+    android:layout_gravity="center_horizontal|bottom">
 
     <LinearLayout
         android:id="@+id/action_buttons"
@@ -58,7 +56,7 @@
             android:visibility="gone" />
 
         <Space
-            android:id="@+id/share_space"
+            android:id="@+id/oav_three_button_space"
             android:layout_width="0dp"
             android:layout_height="1dp"
             android:layout_weight="1"
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index fc06ba0..34ff91d 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -23,5 +23,4 @@
     android:text="@string/recents_clear_all"
     android:textColor="?attr/workspaceTextColor"
     android:textSize="14sp"
-    android:translationY="@dimen/task_thumbnail_half_top_margin"
-    />
\ No newline at end of file
+    android:translationY="@dimen/task_thumbnail_half_top_margin" />
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/layout/overview_panel.xml b/quickstep/res/layout/overview_panel.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/overview_panel.xml
rename to quickstep/res/layout/overview_panel.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml b/quickstep/res/layout/predicted_app_icon.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/predicted_app_icon.xml
rename to quickstep/res/layout/predicted_app_icon.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/res/layout/predicted_hotseat_edu.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
rename to quickstep/res/layout/predicted_hotseat_edu.xml
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 40da136..449fe10 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -16,4 +16,12 @@
 <resources>
     <color name="back_arrow_color_light">#FFFFFFFF</color>
     <color name="back_arrow_color_dark">#99000000</color>
+
+    <color name="chip_hint_foreground_color">#fff</color>
+    <color name="chip_scrim_start_color">#39000000</color>
+
+    <color name="all_apps_label_text">#61000000</color>
+    <color name="all_apps_label_text_dark">#61FFFFFF</color>
+    <color name="all_apps_prediction_row_separator">#3c000000</color>
+    <color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
 </resources>
\ No newline at end of file
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 0f2955b..9ec303a 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -16,6 +16,8 @@
 <resources>
     <string name="task_overlay_factory_class" translatable="false"/>
 
+    <string name="overscroll_plugin_factory_class" translatable="false" />
+
     <!-- Activities which block home gesture -->
     <string-array name="gesture_blocking_activities" translatable="false">
         <item>com.android.launcher3/com.android.quickstep.interaction.GestureSandboxActivity</item>
@@ -35,4 +37,6 @@
     <integer name="assistant_gesture_corner_deg_threshold">20</integer>
 
     <string name="wellbeing_provider_pkg" translatable="false"/>
+
+    <integer name="max_depth_blur_radius">150</integer>
 </resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6737c5f..c85fe6c 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -30,22 +30,18 @@
 
     <dimen name="recents_page_spacing">10dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
-    <dimen name="overview_peek_distance">96dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
     <dimen name="recents_fast_fling_velocity">600dp</dimen>
 
-    <!-- These velocities are in dp / s -->
-    <dimen name="quickstep_fling_threshold_velocity">500dp</dimen>
-    <dimen name="quickstep_fling_min_velocity">250dp</dimen>
-
     <!-- These speeds are in dp / ms -->
     <dimen name="motion_pause_detector_speed_very_slow">0.0285dp</dimen>
     <dimen name="motion_pause_detector_speed_slow">0.15dp</dimen>
     <dimen name="motion_pause_detector_speed_somewhat_fast">0.285dp</dimen>
     <dimen name="motion_pause_detector_speed_fast">1.4dp</dimen>
     <dimen name="motion_pause_detector_min_displacement_from_app">36dp</dimen>
+    <dimen name="quickstep_fling_threshold_speed">0.5dp</dimen>
 
     <!-- Launcher app transition -->
     <dimen name="content_trans_y">50dp</dimen>
@@ -102,4 +98,26 @@
     <dimen name="swipe_edu_circle_size">64dp</dimen>
     <dimen name="swipe_edu_width">80dp</dimen>
     <dimen name="swipe_edu_max_height">184dp</dimen>
+
+    <dimen name="chip_hint_border_width">1dp</dimen>
+    <dimen name="chip_hint_corner_radius">20dp</dimen>
+    <dimen name="chip_hint_outer_padding">20dp</dimen>
+    <dimen name="chip_hint_start_padding">10dp</dimen>
+    <dimen name="chip_hint_end_padding">12dp</dimen>
+    <dimen name="chip_hint_horizontal_margin">20dp</dimen>
+    <dimen name="chip_hint_vertical_offset">16dp</dimen>
+    <dimen name="chip_hint_elevation">2dp</dimen>
+    <dimen name="chip_icon_size">16dp</dimen>
+    <dimen name="chip_text_height">26dp</dimen>
+    <dimen name="chip_text_top_padding">4dp</dimen>
+    <dimen name="chip_text_start_padding">10dp</dimen>
+    <dimen name="chip_text_size">14sp</dimen>
+
+    <dimen name="all_apps_prediction_row_divider_height">17dp</dimen>
+    <dimen name="all_apps_label_top_padding">16dp</dimen>
+    <dimen name="all_apps_label_bottom_padding">8dp</dimen>
+    <dimen name="all_apps_label_text_size">14sp</dimen>
+
+    <!-- Minimum distance to swipe to trigger accessibility gesture -->
+    <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
 </resources>
diff --git a/quickstep/recents_ui_overrides/res/values/override.xml b/quickstep/res/values/override.xml
similarity index 82%
rename from quickstep/recents_ui_overrides/res/values/override.xml
rename to quickstep/res/values/override.xml
index 6aa9619..605774d 100644
--- a/quickstep/recents_ui_overrides/res/values/override.xml
+++ b/quickstep/res/values/override.xml
@@ -25,8 +25,6 @@
 
   <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
 
-  <string name="user_event_dispatcher_class" translatable="false">com.android.quickstep.logging.UserEventDispatcherAppPredictionExtension</string>
+  <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
 
-  <string name="prediction_model_class" translatable="false">com.android.launcher3.hybridhotseat.HotseatPredictionModel</string>
 </resources>
-
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index 22d205a..5cb55ec 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -32,7 +32,7 @@
 import android.view.Surface;
 
 import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 import org.junit.Before;
 import org.junit.Ignore;
@@ -50,7 +50,7 @@
     private OrientationTouchTransformer mTouchTransformer;
 
     Resources mResources;
-    private DefaultDisplay.Info mInfo;
+    private DisplayController.Info mInfo;
 
 
     @Before
@@ -231,12 +231,12 @@
         assertTrue(mTouchTransformer.touchInValidSwipeRegions(inRegion2.getX(), inRegion2.getY()));
     }
 
-    private DefaultDisplay.Info createDisplayInfo(int rotation) {
+    private DisplayController.Info createDisplayInfo(int rotation) {
         Point p = new Point(SIZE_WIDTH, SIZE_HEIGHT);
         if (rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) {
             p = new Point(SIZE_HEIGHT, SIZE_WIDTH);
         }
-        return new DefaultDisplay.Info(0, rotation, 0, p, p, p, null);
+        return new DisplayController.Info(0, rotation, 0, p, p, p, null);
     }
 
     private float generateTouchRegionHeight(int rotation) {
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
index c148a4b..7049af0 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/RecentsActivityTest.java
@@ -60,9 +60,9 @@
 
         FallbackRecentsView frv = activity.getOverviewPanel();
 
-        RunningTaskInfo dummyTask = new RunningTaskInfo();
-        dummyTask.taskId = 22;
-        frv.showCurrentTask(dummyTask);
+        RunningTaskInfo placeholderTask = new RunningTaskInfo();
+        placeholderTask.taskId = 22;
+        frv.showCurrentTask(placeholderTask);
         doLayout(activity);
 
         ThumbnailData thumbnailData = new ThumbnailData();
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index a31ba21..5491daa 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -27,7 +27,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.shadows.LShadowDisplay;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 
@@ -144,7 +144,7 @@
                     LauncherActivityInterface.INSTANCE);
             tvs.setDp(mDeviceProfile);
 
-            int launcherRotation = DefaultDisplay.INSTANCE.get(mContext).getInfo().rotation;
+            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
             if (mAppRotation < 0) {
                 mAppRotation = launcherRotation;
             }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 235df42..68111d2 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -26,12 +26,10 @@
 import android.animation.ValueAnimator;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -40,17 +38,14 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.uioverrides.RecentsViewStateController;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
-import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -73,10 +68,7 @@
             (context, arg1, arg2) -> SystemUiProxy.INSTANCE.get(context).setBackButtonAlpha(
                     Float.intBitsToFloat(arg1), arg2 != 0);
 
-    private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
-
     private OverviewActionsView mActionsView;
-    protected HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -196,7 +188,7 @@
     }
 
     private boolean isOverviewActionsEnabled() {
-        return FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this);
+        return removeShelfFromOverview(this);
     }
 
     public <T extends OverviewActionsView> T getActionsView() {
@@ -225,11 +217,6 @@
     }
 
     @Override
-    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
-        return new QuickstepOnboardingPrefs(this, sharedPrefs);
-    }
-
-    @Override
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
@@ -317,17 +304,6 @@
                 Stream.of(WellbeingModel.SHORTCUT_FACTORY));
     }
 
-    public ShelfPeekAnim getShelfPeekAnim() {
-        return mShelfPeekAnim;
-    }
-
-    /**
-     * Returns Prediction controller for hybrid hotseat
-     */
-    public HotseatPredictionController getHotseatPredictionController() {
-        return mHotseatPredictionController;
-    }
-
     public void setHintUserWillBeActive() {
         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 5ad0bca..199cf63 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -16,7 +16,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
rename to quickstep/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index bd97f65..dddcc86 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -45,7 +45,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
-import android.annotation.TargetApi;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -55,10 +54,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
-import android.os.Build;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Trace;
 import android.util.Pair;
 import android.view.View;
 
@@ -94,7 +93,6 @@
  * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
  * home and/or all-apps.  Not used for 3p launchers.
  */
-@TargetApi(Build.VERSION_CODES.O)
 @SuppressWarnings("unused")
 public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager
         implements OnDeviceProfileChangeListener {
@@ -140,6 +138,7 @@
 
     // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down.
     public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f;
+    public static final String TRANSITION_OPEN_LAUNCHER = "transition:OpenLauncher";
 
     protected final BaseQuickstepLauncher mLauncher;
 
@@ -856,6 +855,21 @@
                 // is initialized.
                 if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
                         || mLauncher.isForceInvisible()) {
+                    if (Trace.isEnabled()) {
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                Trace.beginAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+                                super.onAnimationStart(animation);
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                super.onAnimationEnd(animation);
+                                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+                            }
+                        });
+                    }
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
 
@@ -890,6 +904,9 @@
      */
     private class AppLaunchAnimationRunner implements WrappedAnimationRunnerImpl {
 
+        private static final String TRANSITION_LAUNCH_FROM_RECENTS = "transition:LaunchFromRecents";
+        private static final String TRANSITION_LAUNCH_FROM_ICON = "transition:LaunchFromIcon";
+
         private final Handler mHandler;
         private final View mV;
 
@@ -912,7 +929,8 @@
             boolean launcherClosing =
                     launcherIsATargetWithMode(appTargets, MODE_CLOSING);
 
-            if (isLaunchingFromRecents(mV, appTargets)) {
+            final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets);
+            if (launchingFromRecents) {
                 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets,
                         launcherClosing);
             } else {
@@ -920,6 +938,26 @@
                         launcherClosing);
             }
 
+            if (Trace.isEnabled()) {
+                final String section =
+                        launchingFromRecents
+                                ? TRANSITION_LAUNCH_FROM_RECENTS : TRANSITION_LAUNCH_FROM_ICON;
+
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        Trace.beginAsyncSection(section, 0);
+                        super.onAnimationStart(animation);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        Trace.endAsyncSection(section, 0);
+                    }
+                });
+            }
+
             if (launcherClosing) {
                 anim.addListener(mForceInvisibleListener);
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
rename to quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 8477b10..98bf483 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -19,7 +19,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
 
 import android.os.UserManager;
 
@@ -31,7 +30,6 @@
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.views.ArrowTipView;
-import com.android.systemui.shared.system.LauncherEventUtil;
 
 /**
  * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
@@ -57,8 +55,7 @@
         floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
         ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
             launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
-            launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
-                    ALL_APPS_PREDICTION_TIPS);
+            // TODO: add log to WW
         });
         arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
rename to quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
index 914d9e9..b891120 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AppsDividerView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -42,6 +42,7 @@
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.util.Themes;
 
@@ -90,7 +91,8 @@
         mLauncher = Launcher.getLauncher(context);
 
         boolean isMainColorDark = Themes.getAttrBoolean(context, R.attr.isMainColorDark);
-        mPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
+        mPaint.setStrokeWidth(
+                getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
 
         mStrokeColor = ContextCompat.getColor(context, isMainColorDark
                 ? R.color.all_apps_prediction_row_separator_dark
@@ -134,7 +136,7 @@
                 if (row == this) {
                     break;
                 } else if (row.shouldDraw()) {
-                    sectionCount ++;
+                    sectionCount++;
                 }
             }
 
@@ -181,6 +183,11 @@
     }
 
     private void updateViewVisibility() {
+        // hide divider since we have item decoration for prediction row
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            setVisibility(GONE);
+            return;
+        }
         setVisibility(mDividerType == DividerType.NONE
                 ? GONE
                 : (mIsScrolledOut ? INVISIBLE : VISIBLE));
@@ -303,4 +310,9 @@
     public Class<AppsDividerView> getTypeClass() {
         return AppsDividerView.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return null;
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java b/quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
rename to quickstep/src/com/android/launcher3/appprediction/InstantAppItemInfo.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
similarity index 82%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
rename to quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index e11c701..d3c4c3d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -30,7 +30,6 @@
 import android.os.Build;
 import android.util.AttributeSet;
 import android.util.IntProperty;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -44,10 +43,9 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AllAppsSectionDecorator;
 import com.android.launcher3.allapps.FloatingHeaderRow;
 import com.android.launcher3.allapps.FloatingHeaderView;
 import com.android.launcher3.anim.AlphaUpdateListener;
@@ -56,7 +54,6 @@
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -67,15 +64,12 @@
 import com.android.quickstep.AnimatedFloat;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.P)
 public class PredictionRowView extends LinearLayout implements
         LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
 
-    private static final String TAG = "PredictionRowView";
-
     private static final IntProperty<PredictionRowView> TEXT_ALPHA =
             new IntProperty<PredictionRowView>("textAlpha") {
                 @Override
@@ -93,16 +87,14 @@
             (t) -> (t < 0.8f) ? 0 : (t - 0.8f) / 0.2f;
 
     private final Launcher mLauncher;
-    private final PredictionUiStateManager mPredictionUiStateManager;
     private int mNumPredictedAppsPerRow;
 
-    // The set of predicted app component names
-    private final List<ComponentKeyMapper> mPredictedAppComponents = new ArrayList<>();
-    // The set of predicted apps resolved from the component names and the current set of apps
-    private final ArrayList<ItemInfoWithIcon> mPredictedApps = new ArrayList<>();
     // Helper to drawing the focus indicator.
     private final FocusIndicatorHelper mFocusHelper;
 
+    // The set of predicted apps resolved from the component names and the current set of apps
+    private final List<WorkspaceItemInfo> mPredictedApps = new ArrayList<>();
+
     private final int mIconTextColor;
     private final int mIconFullTextAlpha;
     private int mIconLastSetTextAlpha;
@@ -120,6 +112,8 @@
 
     private boolean mPredictionsEnabled = false;
 
+    AllAppsSectionDecorator.SectionDecorationHandler mDecorationHandler;
+
     public PredictionRowView(@NonNull Context context) {
         this(context, null);
     }
@@ -134,36 +128,24 @@
         mLauncher = Launcher.getLauncher(context);
         mLauncher.addOnDeviceProfileChangeListener(this);
 
-        mPredictionUiStateManager = PredictionUiStateManager.INSTANCE.get(context);
-
         mIconTextColor = Themes.getAttrColor(context, android.R.attr.textColorSecondary);
         mIconFullTextAlpha = Color.alpha(mIconTextColor);
         mIconCurrentTextAlpha = mIconFullTextAlpha;
 
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mDecorationHandler = new AllAppsSectionDecorator.SectionDecorationHandler(getContext(),
+                    false);
+        }
+
         updateVisibility();
     }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-
-        mPredictionUiStateManager.setTargetAppsView(mLauncher.getAppsView());
-        getAppsStore().registerIconContainer(this);
         AllAppsTipView.scheduleShowIfNeeded(mLauncher);
     }
 
-    private AllAppsStore getAppsStore() {
-        return mLauncher.getAppsView().getAppsStore();
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        mPredictionUiStateManager.setTargetAppsView(null);
-        getAppsStore().unregisterIconContainer(this);
-    }
-
     public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) {
         mParent = parent;
     }
@@ -180,6 +162,15 @@
 
     @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.onDraw(canvas);
+            mDecorationHandler.onFocusDraw(canvas, getFocusedChild());
+        }
         mFocusHelper.draw(canvas);
         super.dispatchDraw(canvas);
     }
@@ -193,7 +184,7 @@
 
     @Override
     public boolean shouldDraw() {
-        return getVisibility() != GONE;
+        return getVisibility() == VISIBLE;
     }
 
     @Override
@@ -205,7 +196,7 @@
      * Returns the predicted apps.
      */
     public List<ItemInfoWithIcon> getPredictedApps() {
-        return mPredictedApps;
+        return new ArrayList<>(mPredictedApps);
     }
 
     /**
@@ -217,12 +208,12 @@
      * If the number of predicted apps is the same as the previous list of predicted apps,
      * we can optimize by swapping them in place.
      */
-    public void setPredictedApps(List<ComponentKeyMapper> apps) {
-        mPredictedAppComponents.clear();
-        mPredictedAppComponents.addAll(apps);
-
+    public void setPredictedApps(List<ItemInfo> items) {
         mPredictedApps.clear();
-        mPredictedApps.addAll(processPredictedAppComponents(mPredictedAppComponents));
+        items.stream()
+                .filter(itemInfo -> itemInfo instanceof WorkspaceItemInfo)
+                .map(itemInfo -> (WorkspaceItemInfo) itemInfo)
+                .forEach(mPredictedApps::add);
         applyPredictionApps();
     }
 
@@ -264,11 +255,7 @@
             icon.reset();
             if (predictionCount > i) {
                 icon.setVisibility(View.VISIBLE);
-                if (mPredictedApps.get(i) instanceof AppInfo) {
-                    icon.applyFromApplicationInfo((AppInfo) mPredictedApps.get(i));
-                } else if (mPredictedApps.get(i) instanceof WorkspaceItemInfo) {
-                    icon.applyFromWorkspaceItem((WorkspaceItemInfo) mPredictedApps.get(i));
-                }
+                icon.applyFromWorkspaceItem(mPredictedApps.get(i));
                 icon.setTextColor(iconColor);
             } else {
                 icon.setVisibility(predictionCount == 0 ? GONE : INVISIBLE);
@@ -284,33 +271,6 @@
         mParent.onHeightUpdated();
     }
 
-    private List<ItemInfoWithIcon> processPredictedAppComponents(
-            List<ComponentKeyMapper> components) {
-        if (getAppsStore().getApps().length == 0) {
-            // Apps have not been bound yet.
-            return Collections.emptyList();
-        }
-
-        List<ItemInfoWithIcon> predictedApps = new ArrayList<>();
-        for (ComponentKeyMapper mapper : components) {
-            ItemInfoWithIcon info = mapper.getApp(getAppsStore());
-            if (info != null) {
-                ItemInfoWithIcon predictedApp = info.clone();
-                predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
-                predictedApps.add(predictedApp);
-            } else {
-                if (FeatureFlags.IS_STUDIO_BUILD) {
-                    Log.e(TAG, "Predicted app not found: " + mapper);
-                }
-            }
-            // Stop at the number of predicted apps
-            if (predictedApps.size() == mNumPredictedAppsPerRow) {
-                break;
-            }
-        }
-        return predictedApps;
-    }
-
     @Override
     public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
             ArrayList<LauncherLogProto.Target> parents) {
@@ -405,4 +365,9 @@
     public Class<PredictionRowView> getTypeClass() {
         return PredictionRowView.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return getChildAt(0);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduActivity.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 4f95254..4451e7a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -47,35 +47,27 @@
  */
 public class HotseatEduController {
 
-    public static final String HOTSEAT_EDU_ACTION =
-            "com.android.launcher3.action.SHOW_HYBRID_HOTSEAT_EDU";
     public static final String SETTINGS_ACTION =
             "android.settings.ACTION_CONTENT_SUGGESTIONS_SETTINGS";
 
     private final Launcher mLauncher;
     private final Hotseat mHotseat;
-    private HotseatRestoreHelper mRestoreHelper;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
     private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
     private IntArray mNewScreens = null;
-    private Runnable mOnOnboardingComplete;
 
-    HotseatEduController(Launcher launcher, HotseatRestoreHelper restoreHelper, Runnable runnable) {
+    HotseatEduController(Launcher launcher) {
         mLauncher = launcher;
         mHotseat = launcher.getHotseat();
-        mRestoreHelper = restoreHelper;
-        mOnOnboardingComplete = runnable;
     }
 
     /**
      * Checks what type of migration should be used and migrates hotseat
      */
     void migrate() {
-        if (mRestoreHelper != null) {
-            mRestoreHelper.createBackup();
-        }
+        HotseatRestoreHelper.createBackup(mLauncher);
         if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
             migrateToFolder();
         } else {
@@ -227,7 +219,7 @@
     }
 
     void finishOnboarding() {
-        mOnOnboardingComplete.run();
+        mLauncher.getModel().onWorkspaceUiChanged();
     }
 
     void showDimissTip() {
@@ -284,4 +276,3 @@
         return new Intent(SETTINGS_ACTION).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
     }
 }
-
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 2b3f395..39bf008 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -40,7 +40,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.AbstractSlideInView;
 
 import java.util.List;
@@ -123,16 +122,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.TIP;
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatFileLog.java
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
new file mode 100644
index 0000000..151a113
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.hybridhotseat;
+
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.ComponentName;
+import android.os.Process;
+import android.view.HapticFeedbackConstants;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.PredictedHotseatContainer;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.views.ArrowTipView;
+import com.android.launcher3.views.Snackbar;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Provides prediction ability for the hotseat. Fills gaps in hotseat with predicted items, allows
+ * pinning of predicted apps and manages replacement of predicted apps with user drag.
+ */
+public class HotseatPredictionController implements DragController.DragListener,
+        SystemShortcut.Factory<QuickstepLauncher>, InvariantDeviceProfile.OnIDPChangeListener,
+        DragSource {
+
+    private int mHotSeatItemsCount;
+
+    private Launcher mLauncher;
+    private final Hotseat mHotseat;
+
+    private List<ItemInfo> mPredictedItems = Collections.emptyList();
+
+    private AnimatorSet mIconRemoveAnimators;
+    private boolean mUIUpdatePaused = false;
+    private boolean mDragInProgress = false;
+
+    private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
+
+    private final View.OnLongClickListener mPredictionLongClickListener = v -> {
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+        if (mLauncher.getWorkspace().isSwitchingState()) return false;
+        if (!mLauncher.getOnboardingPrefs().getBoolean(
+                OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN)) {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(getSettingsIntent()));
+            mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.HOTSEAT_LONGPRESS_TIP_SEEN);
+            mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            return true;
+        }
+
+        // Start the drag
+        // Use a new itemInfo so that the original predicted item is stable
+        WorkspaceItemInfo dragItem = new WorkspaceItemInfo((WorkspaceItemInfo) v.getTag());
+        v.setVisibility(View.INVISIBLE);
+        mLauncher.getWorkspace().beginDragShared(
+                v, null, this, dragItem, new DragPreviewProvider(v), new DragOptions());
+        return true;
+    };
+
+    public HotseatPredictionController(Launcher launcher) {
+        mLauncher = launcher;
+        mHotseat = launcher.getHotseat();
+        mHotSeatItemsCount = mLauncher.getDeviceProfile().inv.numHotseatIcons;
+        mLauncher.getDragController().addDragListener(this);
+
+        launcher.getDeviceProfile().inv.addOnChangeListener(this);
+    }
+
+    /**
+     * Shows appropriate hotseat education based on prediction enabled and migration states.
+     */
+    public void showEdu() {
+        mLauncher.getStateManager().goToState(NORMAL, true, () -> {
+            if (mPredictedItems.isEmpty()) {
+                // launcher has empty predictions set
+                Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
+                        R.string.hotseat_prediction_settings, null,
+                        () -> mLauncher.startActivity(getSettingsIntent()));
+            } else if (getPredictedIcons().size() >= (mHotSeatItemsCount + 1) / 2) {
+                showDiscoveryTip();
+            } else {
+                HotseatEduController eduController = new HotseatEduController(mLauncher);
+                eduController.setPredictedApps(mPredictedItems.stream()
+                        .map(i -> (WorkspaceItemInfo) i)
+                        .collect(Collectors.toList()));
+                eduController.showEdu();
+            }
+        });
+    }
+
+    /**
+     * Shows educational tip for hotseat if user does not go through Tips app.
+     */
+    private void showDiscoveryTip() {
+        if (getPredictedIcons().isEmpty()) {
+            new ArrowTipView(mLauncher).show(
+                    mLauncher.getString(R.string.hotseat_tip_no_empty_slots), mHotseat.getTop());
+        } else {
+            Snackbar.show(mLauncher, R.string.hotseat_tip_gaps_filled,
+                    R.string.hotseat_prediction_settings, null,
+                    () -> mLauncher.startActivity(getSettingsIntent()));
+        }
+    }
+
+    /**
+     * Returns if hotseat client has predictions
+     */
+    public boolean hasPredictions() {
+        return !mPredictedItems.isEmpty();
+    }
+
+    private void fillGapsWithPrediction() {
+        fillGapsWithPrediction(false, null);
+    }
+
+    private void fillGapsWithPrediction(boolean animate, Runnable callback) {
+        if (mUIUpdatePaused || mDragInProgress) {
+            return;
+        }
+
+        int predictionIndex = 0;
+        ArrayList<WorkspaceItemInfo> newItems = new ArrayList<>();
+        // make sure predicted icon removal and filling predictions don't step on each other
+        if (mIconRemoveAnimators != null && mIconRemoveAnimators.isRunning()) {
+            mIconRemoveAnimators.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    fillGapsWithPrediction(animate, callback);
+                    mIconRemoveAnimators.removeListener(this);
+                }
+            });
+            return;
+        }
+        for (int rank = 0; rank < mHotSeatItemsCount; rank++) {
+            View child = mHotseat.getChildAt(
+                    mHotseat.getCellXFromOrder(rank),
+                    mHotseat.getCellYFromOrder(rank));
+
+            if (child != null && !isPredictedIcon(child)) {
+                continue;
+            }
+            if (mPredictedItems.size() <= predictionIndex) {
+                // Remove predicted apps from the past
+                if (isPredictedIcon(child)) {
+                    mHotseat.removeView(child);
+                }
+                continue;
+            }
+            WorkspaceItemInfo predictedItem =
+                    (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
+            if (isPredictedIcon(child) && child.isEnabled()) {
+                PredictedAppIcon icon = (PredictedAppIcon) child;
+                icon.applyFromWorkspaceItem(predictedItem);
+                icon.finishBinding(mPredictionLongClickListener);
+            } else {
+                newItems.add(predictedItem);
+            }
+            preparePredictionInfo(predictedItem, rank);
+        }
+        bindItems(newItems, animate, callback);
+    }
+
+    private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate, Runnable callback) {
+        AnimatorSet animationSet = new AnimatorSet();
+        for (WorkspaceItemInfo item : itemsToAdd) {
+            PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
+            mLauncher.getWorkspace().addInScreenFromBind(icon, item);
+            icon.finishBinding(mPredictionLongClickListener);
+            if (animate) {
+                animationSet.play(ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0.2f, 1));
+            }
+        }
+        if (animate) {
+            if (callback != null) {
+                animationSet.addListener(AnimationSuccessListener.forRunnable(callback));
+            }
+            animationSet.start();
+        } else {
+            if (callback != null) callback.run();
+        }
+    }
+
+    /**
+     * Unregisters callbacks and frees resources
+     */
+    public void destroy() {
+        mLauncher.getDeviceProfile().inv.removeOnChangeListener(this);
+    }
+
+    /**
+     * start and pauses predicted apps update on the hotseat
+     */
+    public void setPauseUIUpdate(boolean paused) {
+        mUIUpdatePaused = paused;
+        if (!paused) {
+            fillGapsWithPrediction();
+        }
+    }
+
+    /**
+     * Sets or updates the predicted items
+     */
+    public void setPredictedItems(FixedContainerItems items) {
+        mPredictedItems = items.items;
+        if (mPredictedItems.isEmpty()) {
+            HotseatRestoreHelper.restoreBackup(mLauncher);
+        }
+        fillGapsWithPrediction();
+    }
+
+    /**
+     * Pins a predicted app icon into place.
+     */
+    public void pinPrediction(ItemInfo info) {
+        PredictedAppIcon icon = (PredictedAppIcon) mHotseat.getChildAt(
+                mHotseat.getCellXFromOrder(info.rank),
+                mHotseat.getCellYFromOrder(info.rank));
+        if (icon == null) {
+            return;
+        }
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo((WorkspaceItemInfo) info);
+        mLauncher.getModelWriter().addItemToDatabase(workspaceItemInfo,
+                LauncherSettings.Favorites.CONTAINER_HOTSEAT, workspaceItemInfo.screenId,
+                workspaceItemInfo.cellX, workspaceItemInfo.cellY);
+        ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 1, 0.8f, 1).start();
+        icon.pin(workspaceItemInfo);
+        mLauncher.getStatsLogManager().logger()
+                .withItemInfo(workspaceItemInfo)
+                .log(LAUNCHER_HOTSEAT_PREDICTION_PINNED);
+    }
+
+    private List<PredictedAppIcon> getPredictedIcons() {
+        List<PredictedAppIcon> icons = new ArrayList<>();
+        ViewGroup vg = mHotseat.getShortcutsAndWidgets();
+        for (int i = 0; i < vg.getChildCount(); i++) {
+            View child = vg.getChildAt(i);
+            if (isPredictedIcon(child)) {
+                icons.add((PredictedAppIcon) child);
+            }
+        }
+        return icons;
+    }
+
+    private void removePredictedApps(List<PredictedAppIcon.PredictedIconOutlineDrawing> outlines,
+            DropTarget.DragObject dragObject) {
+        if (mIconRemoveAnimators != null) {
+            mIconRemoveAnimators.end();
+        }
+        mIconRemoveAnimators = new AnimatorSet();
+        removeOutlineDrawings();
+        for (PredictedAppIcon icon : getPredictedIcons()) {
+            if (!icon.isEnabled()) {
+                continue;
+            }
+            if (dragObject.dragSource == this && icon.equals(dragObject.originalView)) {
+                mHotseat.removeView(icon);
+                continue;
+            }
+            int rank = ((WorkspaceItemInfo) icon.getTag()).rank;
+            outlines.add(new PredictedAppIcon.PredictedIconOutlineDrawing(
+                    mHotseat.getCellXFromOrder(rank), mHotseat.getCellYFromOrder(rank), icon));
+            icon.setEnabled(false);
+            ObjectAnimator animator = ObjectAnimator.ofFloat(icon, SCALE_PROPERTY, 0);
+            animator.addListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    if (icon.getParent() != null) {
+                        mHotseat.removeView(icon);
+                    }
+                }
+            });
+            mIconRemoveAnimators.play(animator);
+        }
+        mIconRemoveAnimators.start();
+    }
+
+    @Override
+    public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
+        removePredictedApps(mOutlineDrawings, dragObject);
+        if (mOutlineDrawings.isEmpty()) return;
+        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+            mHotseat.addDelegatedCellDrawing(outlineDrawing);
+        }
+        mDragInProgress = true;
+        mHotseat.invalidate();
+    }
+
+    @Override
+    public void onDragEnd() {
+        mDragInProgress = false;
+        fillGapsWithPrediction(true, this::removeOutlineDrawings);
+    }
+
+    @Nullable
+    @Override
+    public SystemShortcut<QuickstepLauncher> getShortcut(QuickstepLauncher activity,
+            ItemInfo itemInfo) {
+        if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            return null;
+        }
+        return new PinPrediction(activity, itemInfo);
+    }
+
+    private void preparePredictionInfo(WorkspaceItemInfo itemInfo, int rank) {
+        itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+        itemInfo.rank = rank;
+        itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+        itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+        itemInfo.screenId = rank;
+    }
+
+    private void removeOutlineDrawings() {
+        if (mOutlineDrawings.isEmpty()) return;
+        for (PredictedAppIcon.PredictedIconOutlineDrawing outlineDrawing : mOutlineDrawings) {
+            mHotseat.removeDelegatedCellDrawing(outlineDrawing);
+        }
+        mHotseat.invalidate();
+        mOutlineDrawings.clear();
+    }
+
+    @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+        this.mHotSeatItemsCount = profile.numHotseatIcons;
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+        //Does nothing
+    }
+
+    /**
+     * Logs rank info based on current list of predicted items
+     */
+    public void logLaunchedAppRankingInfo(@NonNull ItemInfo itemInfo, InstanceId instanceId) {
+        if (Utilities.IS_DEBUG_DEVICE) {
+            final String pkg = itemInfo.getTargetComponent() != null
+                    ? itemInfo.getTargetComponent().getPackageName() : "unknown";
+            HotseatFileLog.INSTANCE.get(mLauncher).log("UserEvent",
+                    "appLaunch: packageName:" + pkg + ",isWorkApp:" + (itemInfo.user != null
+                            && !Process.myUserHandle().equals(itemInfo.user))
+                            + ",launchLocation:" + itemInfo.container);
+        }
+
+
+        ComponentName targetCN = itemInfo.getTargetComponent();
+        if (targetCN == null) {
+            return;
+        }
+        int rank = -1;
+        for (int i = mPredictedItems.size() - 1; i >= 0; i--) {
+            ItemInfo info = mPredictedItems.get(i);
+            if (targetCN.equals(info.getTargetComponent()) && itemInfo.user.equals(info.user)) {
+                rank = i;
+                break;
+            }
+        }
+        if (rank < 0) {
+            return;
+        }
+
+        int cardinality = 0;
+        for (PredictedAppIcon icon : getPredictedIcons()) {
+            ItemInfo info = (ItemInfo) icon.getTag();
+            cardinality |= 1 << info.screenId;
+        }
+
+        PredictedHotseatContainer.Builder containerBuilder = PredictedHotseatContainer.newBuilder();
+        containerBuilder.setCardinality(cardinality);
+        if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            containerBuilder.setIndex(rank);
+        }
+        mLauncher.getStatsLogManager().logger()
+                .withInstanceId(instanceId)
+                .withRank(rank)
+                .withContainerInfo(ContainerInfo.newBuilder()
+                        .setPredictedHotseatContainer(containerBuilder)
+                        .build())
+                .log(LAUNCHER_HOTSEAT_RANKED);
+    }
+
+    private class PinPrediction extends SystemShortcut<QuickstepLauncher> {
+
+        private PinPrediction(QuickstepLauncher target, ItemInfo itemInfo) {
+            super(R.drawable.ic_pin, R.string.pin_prediction, target,
+                    itemInfo);
+        }
+
+        @Override
+        public void onClick(View view) {
+            dismissTaskMenuView(mTarget);
+            pinPrediction(mItemInfo);
+        }
+    }
+
+    private static boolean isPredictedIcon(View view) {
+        return view instanceof PredictedAppIcon && view.getTag() instanceof WorkspaceItemInfo
+                && ((WorkspaceItemInfo) view.getTag()).container
+                == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
similarity index 66%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 5a038d2..8f31c22 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.hybridhotseat;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
@@ -24,13 +24,10 @@
 import android.content.Context;
 import android.os.Bundle;
 
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.model.AllAppsList;
-import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.PredictionModel;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -38,55 +35,48 @@
 
 import java.util.ArrayList;
 import java.util.Locale;
-import java.util.function.Consumer;
 
 /**
  * Model helper for app predictions in workspace
  */
-public class HotseatPredictionModel extends PredictionModel {
+public class HotseatPredictionModel {
     private static final String APP_LOCATION_HOTSEAT = "hotseat";
     private static final String APP_LOCATION_WORKSPACE = "workspace";
 
     private static final String BUNDLE_KEY_PIN_EVENTS = "pin_events";
     private static final String BUNDLE_KEY_CURRENT_ITEMS = "current_items";
 
-
-    public HotseatPredictionModel(Context context) { }
-
     /**
-     * Creates and returns bundle using workspace items and cached items
+     * Creates and returns bundle using workspace items
      */
-    public void createBundle(Consumer<Bundle> cb) {
-        LauncherAppState appState = LauncherAppState.getInstance(mContext);
-        appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
-            @Override
-            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                Bundle bundle = new Bundle();
-                ArrayList<AppTargetEvent> events = new ArrayList<>();
-                ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
-                workspaceItems.addAll(dataModel.appWidgets);
-                for (ItemInfo item : workspaceItems) {
-                    AppTarget target = getAppTargetFromInfo(item);
-                    if (target != null && !isTrackedForPrediction(item)) continue;
-                    events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
-                }
-                ArrayList<AppTarget> currentTargets = new ArrayList<>();
-                for (ItemInfo itemInfo : dataModel.cachedPredictedItems) {
-                    AppTarget target = getAppTargetFromInfo(itemInfo);
-                    if (target != null) currentTargets.add(target);
-                }
-                bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
-                bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
-                MAIN_EXECUTOR.execute(() -> cb.accept(bundle));
+    public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
+        Bundle bundle = new Bundle();
+        ArrayList<AppTargetEvent> events = new ArrayList<>();
+        ArrayList<ItemInfo> workspaceItems = new ArrayList<>(dataModel.workspaceItems);
+        workspaceItems.addAll(dataModel.appWidgets);
+        for (ItemInfo item : workspaceItems) {
+            AppTarget target = getAppTargetFromInfo(context, item);
+            if (target != null && !isTrackedForPrediction(item)) continue;
+            events.add(wrapAppTargetWithLocation(target, AppTargetEvent.ACTION_PIN, item));
+        }
+        ArrayList<AppTarget> currentTargets = new ArrayList<>();
+        FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
+        if (hotseatItems != null) {
+            for (ItemInfo itemInfo : hotseatItems.items) {
+                AppTarget target = getAppTargetFromInfo(context, itemInfo);
+                if (target != null) currentTargets.add(target);
             }
-        });
+        }
+        bundle.putParcelableArrayList(BUNDLE_KEY_PIN_EVENTS, events);
+        bundle.putParcelableArrayList(BUNDLE_KEY_CURRENT_ITEMS, currentTargets);
+        return bundle;
     }
 
     /**
      * Creates and returns for {@link AppTarget} object given an {@link ItemInfo}. Returns null
      * if item is not supported prediction
      */
-    public AppTarget getAppTargetFromInfo(ItemInfo info) {
+    public static AppTarget getAppTargetFromInfo(Context context, ItemInfo info) {
         if (info == null) return null;
         if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
                 && info instanceof LauncherAppWidgetInfo
@@ -107,17 +97,17 @@
                     shortcutKey.componentName.getPackageName(), shortcutKey.user).build();
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
             return new AppTarget.Builder(new AppTargetId("folder:" + info.id),
-                    mContext.getPackageName(), info.user).build();
+                    context.getPackageName(), info.user).build();
         }
         return null;
     }
 
-
     /**
      * Creates and returns {@link AppTargetEvent} from an {@link AppTarget}, action, and item
      * location using {@link ItemInfo}
      */
-    public AppTargetEvent wrapAppTargetWithLocation(AppTarget target, int action, ItemInfo info) {
+    public static AppTargetEvent wrapAppTargetWithLocation(
+            AppTarget target, int action, ItemInfo info) {
         String location = String.format(Locale.ENGLISH, "%s/%d/[%d,%d]/[%d,%d]",
                 info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                         ? APP_LOCATION_HOTSEAT : APP_LOCATION_WORKSPACE,
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
similarity index 81%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
rename to quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
index 9e7c9fb..90f762e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatRestoreHelper.java
@@ -19,8 +19,10 @@
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.Context;
+
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.GridBackupTable;
 import com.android.launcher3.provider.LauncherDbUtils;
@@ -29,29 +31,24 @@
  * A helper class to manage migration revert restoration for hybrid hotseat
  */
 public class HotseatRestoreHelper {
-    private final Launcher mLauncher;
-
-    HotseatRestoreHelper(Launcher context) {
-        mLauncher = context;
-    }
 
     /**
      * Creates a snapshot backup of Favorite table for future restoration use.
      */
-    public void createBackup() {
+    public static void createBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
             try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
                     LauncherSettings.Settings.call(
-                            mLauncher.getContentResolver(),
+                            context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
                             .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
-                InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
-                GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                GridBackupTable backupTable = new GridBackupTable(context,
                         transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
                         idp.numRows);
                 backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
                 transaction.commit();
-                LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                LauncherSettings.Settings.call(context.getContentResolver(),
                         LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
             }
         });
@@ -60,23 +57,23 @@
     /**
      * Finds and restores a previously saved snapshow of Favorites table
      */
-    public void restoreBackup() {
+    public static void restoreBackup(Context context) {
         MODEL_EXECUTOR.execute(() -> {
             try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
                     LauncherSettings.Settings.call(
-                            mLauncher.getContentResolver(),
+                            context.getContentResolver(),
                             LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
                             .getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
                 if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
                     return;
                 }
-                InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
-                GridBackupTable backupTable = new GridBackupTable(mLauncher,
+                InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                GridBackupTable backupTable = new GridBackupTable(context,
                         transaction.getDb(), idp.numHotseatIcons, idp.numColumns,
                         idp.numRows);
                 backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
                 transaction.commit();
-                mLauncher.getModel().forceReload();
+                LauncherAppState.getInstance(context).getModel().forceReload();
             }
         });
     }
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
new file mode 100644
index 0000000..364a321
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model;
+
+import static android.app.prediction.AppTargetEvent.ACTION_DISMISS;
+import static android.app.prediction.AppTargetEvent.ACTION_LAUNCH;
+import static android.app.prediction.AppTargetEvent.ACTION_PIN;
+import static android.app.prediction.AppTargetEvent.ACTION_UNPIN;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DRAG_STARTED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_FOLDER_CREATED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.annotation.TargetApi;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.text.TextUtils;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
+import com.android.launcher3.logger.LauncherAtom.FolderContainer;
+import com.android.launcher3.logger.LauncherAtom.HotseatContainer;
+import com.android.launcher3.logger.LauncherAtom.WorkspaceContainer;
+import com.android.launcher3.logging.StatsLogManager.EventEnum;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
+
+import java.util.Locale;
+import java.util.function.ObjIntConsumer;
+import java.util.function.Predicate;
+
+/**
+ * Utility class to track stats log and emit corresponding app events
+ */
+@TargetApi(Build.VERSION_CODES.R)
+public class AppEventProducer implements StatsLogConsumer {
+
+    private static final int MSG_LAUNCH = 0;
+
+    private final Context mContext;
+    private final Handler mMessageHandler;
+    private final ObjIntConsumer<AppTargetEvent> mCallback;
+
+    private LauncherAtom.ItemInfo mLastDragItem;
+
+    public AppEventProducer(Context context, ObjIntConsumer<AppTargetEvent> callback) {
+        mContext = context;
+        mMessageHandler = new Handler(MODEL_EXECUTOR.getLooper(), this::handleMessage);
+        mCallback = callback;
+    }
+
+    @WorkerThread
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_LAUNCH: {
+                mCallback.accept((AppTargetEvent) msg.obj, msg.arg1);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @AnyThread
+    private void sendEvent(LauncherAtom.ItemInfo atomInfo, int eventId, int targetPredictor) {
+        sendEvent(toAppTarget(atomInfo), atomInfo, eventId, targetPredictor);
+    }
+
+    @AnyThread
+    private void sendEvent(AppTarget target, LauncherAtom.ItemInfo locationInfo, int eventId,
+            int targetPredictor) {
+        if (target != null) {
+            AppTargetEvent event = new AppTargetEvent.Builder(target, eventId)
+                    .setLaunchLocation(getContainer(locationInfo))
+                    .build();
+            mMessageHandler.obtainMessage(MSG_LAUNCH, targetPredictor, 0, event).sendToTarget();
+        }
+    }
+
+    @Override
+    public void consume(EventEnum event, LauncherAtom.ItemInfo atomInfo) {
+        if (event == LAUNCHER_APP_LAUNCH_TAP
+                || event == LAUNCHER_TASK_LAUNCH_SWIPE_DOWN
+                || event == LAUNCHER_TASK_LAUNCH_TAP
+                || event == LAUNCHER_QUICKSWITCH_RIGHT
+                || event == LAUNCHER_QUICKSWITCH_LEFT) {
+            sendEvent(atomInfo, ACTION_LAUNCH, CONTAINER_PREDICTION);
+        } else if (event == LAUNCHER_ITEM_DROPPED_ON_DONT_SUGGEST) {
+            sendEvent(atomInfo, ACTION_DISMISS, CONTAINER_PREDICTION);
+        } else if (event == LAUNCHER_ITEM_DRAG_STARTED) {
+            mLastDragItem = atomInfo;
+        } else if (event == LAUNCHER_ITEM_DROP_COMPLETED) {
+            if (mLastDragItem == null) {
+                return;
+            }
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+            if (isTrackedForHotseatPrediction(mLastDragItem)) {
+                sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+            mLastDragItem = null;
+        } else if (event == LAUNCHER_ITEM_DROP_FOLDER_CREATED) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(createTempFolderTarget(), atomInfo, ACTION_PIN,
+                        CONTAINER_HOTSEAT_PREDICTION);
+                sendEvent(atomInfo, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_FOLDER_CONVERTED_TO_ICON) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(createTempFolderTarget(), atomInfo, ACTION_UNPIN,
+                        CONTAINER_HOTSEAT_PREDICTION);
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_ITEM_DROPPED_ON_REMOVE) {
+            if (mLastDragItem != null && isTrackedForHotseatPrediction(mLastDragItem)) {
+                sendEvent(mLastDragItem, ACTION_UNPIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        } else if (event == LAUNCHER_HOTSEAT_PREDICTION_PINNED) {
+            if (isTrackedForHotseatPrediction(atomInfo)) {
+                sendEvent(atomInfo, ACTION_PIN, CONTAINER_HOTSEAT_PREDICTION);
+            }
+        }
+    }
+
+    @Nullable
+    private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+        UserHandle userHandle = Process.myUserHandle();
+        if (info.getIsWork()) {
+            userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
+                    .filter(((Predicate<UserHandle>) userHandle::equals).negate())
+                    .findAny()
+                    .orElse(null);
+        }
+        if (userHandle == null) {
+            return null;
+        }
+        ComponentName cn = null;
+        String id = null;
+
+        switch (info.getItemCase()) {
+            case APPLICATION: {
+                LauncherAtom.Application app = info.getApplication();
+                if ((cn = parseNullable(app.getComponentName())) != null) {
+                    id = "app:" + cn.getPackageName();
+                }
+                break;
+            }
+            case SHORTCUT: {
+                LauncherAtom.Shortcut si = info.getShortcut();
+                if (!TextUtils.isEmpty(si.getShortcutId())
+                        && (cn = parseNullable(si.getShortcutName())) != null) {
+                    id = "shortcut:" + si.getShortcutId();
+                }
+                break;
+            }
+            case WIDGET: {
+                LauncherAtom.Widget widget = info.getWidget();
+                if ((cn = parseNullable(widget.getComponentName())) != null) {
+                    id = "widget:" + cn.getPackageName();
+                }
+                break;
+            }
+            case TASK: {
+                LauncherAtom.Task task = info.getTask();
+                if ((cn = parseNullable(task.getComponentName())) != null) {
+                    id = "app:" + cn.getPackageName();
+                }
+                break;
+            }
+            case FOLDER_ICON:
+                return createTempFolderTarget();
+        }
+        if (id != null && cn != null) {
+            return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
+                    .setClassName(cn.getClassName())
+                    .build();
+        }
+        return null;
+    }
+
+    private AppTarget createTempFolderTarget() {
+        return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
+                mContext.getPackageName(), Process.myUserHandle())
+                .build();
+    }
+
+    private String getContainer(LauncherAtom.ItemInfo info) {
+        ContainerInfo ci = info.getContainerInfo();
+        switch (ci.getContainerCase()) {
+            case WORKSPACE: {
+                // In case the item type is not widgets, the spaceX and spanY default to 1.
+                int spanX = info.getWidget().getSpanX();
+                int spanY = info.getWidget().getSpanY();
+                return getWorkspaceContainerString(ci.getWorkspace(), spanX, spanY);
+            }
+            case HOTSEAT: {
+                return getHotseatContainerString(ci.getHotseat());
+            }
+            case TASK_SWITCHER_CONTAINER: {
+                return "task-switcher";
+            }
+            case ALL_APPS_CONTAINER: {
+                return "all-apps";
+            }
+            case SEARCH_RESULT_CONTAINER: {
+                return "search-results";
+            }
+            case PREDICTED_HOTSEAT_CONTAINER: {
+                return "predictions/hotseat";
+            }
+            case PREDICTION_CONTAINER: {
+                return "predictions";
+            }
+            case FOLDER: {
+                FolderContainer fc = ci.getFolder();
+                switch (fc.getParentContainerCase()) {
+                    case WORKSPACE:
+                        return "folder/" + getWorkspaceContainerString(fc.getWorkspace(), 1, 1);
+                    case HOTSEAT:
+                        return "folder/" + getHotseatContainerString(fc.getHotseat());
+                }
+                return "folder";
+            }
+        }
+        return "";
+    }
+
+    private static String getWorkspaceContainerString(WorkspaceContainer wc, int spanX, int spanY) {
+        return String.format(Locale.ENGLISH, "workspace/%d/[%d,%d]/[%d,%d]",
+                wc.getPageIndex(), wc.getGridX(), wc.getGridY(), spanX, spanY);
+    }
+
+    private static String getHotseatContainerString(HotseatContainer hc) {
+        return String.format(Locale.ENGLISH, "hotseat/%1$d/[%1$d,0]/[1,1]", hc.getIndex());
+    }
+
+    private static ComponentName parseNullable(String componentNameString) {
+        return TextUtils.isEmpty(componentNameString)
+                ? null : ComponentName.unflattenFromString(componentNameString);
+    }
+
+    /**
+     * Helper method to determine if {@link ItemInfo} should be tracked and reported to predictors
+     */
+    private static boolean isTrackedForHotseatPrediction(LauncherAtom.ItemInfo info) {
+        ContainerInfo ci = info.getContainerInfo();
+        switch (ci.getContainerCase()) {
+            case HOTSEAT:
+                return true;
+            case WORKSPACE:
+                return ci.getWorkspace().getPageIndex() == 0;
+            default:
+                return false;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
new file mode 100644
index 0000000..b0fba3d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -0,0 +1,119 @@
+/*
+ * 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.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
+import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
+
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.Utilities;
+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.WorkspaceItemInfo;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Task to update model as a result of predicted apps update
+ */
+public class PredictionUpdateTask extends BaseModelUpdateTask {
+
+    private final List<AppTarget> mTargets;
+    private final PredictorState mPredictorState;
+
+    PredictionUpdateTask(PredictorState predictorState, List<AppTarget> targets) {
+        mPredictorState = predictorState;
+        mTargets = targets;
+    }
+
+    @Override
+    public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+        Context context = app.getContext();
+
+        // TODO: remove this
+        Utilities.getDevicePrefs(context).edit()
+                .putBoolean(LAST_PREDICTION_ENABLED_STATE, !mTargets.isEmpty()).apply();
+
+        FixedContainerItems fci = mPredictorState.items;
+        Set<UserHandle> usersForChangedShortcuts = new HashSet<>(fci.items.stream()
+                .filter(info -> info.itemType == ITEM_TYPE_DEEP_SHORTCUT)
+                .map(info -> info.user)
+                .collect(Collectors.toSet()));
+        fci.items.clear();
+
+        for (AppTarget target : mTargets) {
+            WorkspaceItemInfo itemInfo;
+            ShortcutInfo si = target.getShortcutInfo();
+            if (si != null) {
+                usersForChangedShortcuts.add(si.getUserHandle());
+                itemInfo = new WorkspaceItemInfo(si, context);
+                app.getIconCache().getShortcutIcon(itemInfo, si);
+            } else {
+                String className = target.getClassName();
+                if (COMPONENT_CLASS_MARKER.equals(className)) {
+                    // TODO: Implement this
+                    continue;
+                }
+                ComponentName cn = new ComponentName(target.getPackageName(), className);
+                UserHandle user = target.getUser();
+                itemInfo = apps.data.stream()
+                        .filter(info -> user.equals(info.user) && cn.equals(info.componentName))
+                        .map(ai -> {
+                            app.getIconCache().getTitleAndIcon(ai, false);
+                            return ai.makeWorkspaceItem();
+                        })
+                        .findAny()
+                        .orElseGet(() -> {
+                            LauncherActivityInfo lai = context.getSystemService(LauncherApps.class)
+                                    .resolveActivity(AppInfo.makeLaunchIntent(cn), user);
+                            if (lai == null) {
+                                return null;
+                            }
+                            AppInfo ai = new AppInfo(context, lai, user);
+                            app.getIconCache().getTitleAndIcon(ai, lai, false);
+                            return ai.makeWorkspaceItem();
+                        });
+
+                if (itemInfo == null) {
+                    continue;
+                }
+            }
+
+            itemInfo.container = fci.containerId;
+            fci.items.add(itemInfo);
+        }
+
+        bindExtraContainerItems(fci);
+        usersForChangedShortcuts.forEach(
+                u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
+
+        // Save to disk
+        mPredictorState.storage.write(context, fci.items);
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
new file mode 100644
index 0000000..be57dec
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -0,0 +1,299 @@
+/*
+ * 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.model;
+
+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.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionManager;
+import android.app.prediction.AppPredictor;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.PersistedItemArray;
+import com.android.quickstep.logging.StatsLogCompatManager;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.IntStream;
+
+/**
+ * Model delegate which loads prediction items
+ */
+public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
+
+    public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+
+    private final PredictorState mAllAppsState =
+            new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
+    private final PredictorState mHotseatState =
+            new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
+
+    private final InvariantDeviceProfile mIDP;
+    private final AppEventProducer mAppEventProducer;
+
+    private boolean mActive = false;
+
+    public QuickstepModelDelegate(Context context) {
+        mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
+
+        mIDP = InvariantDeviceProfile.INSTANCE.get(context);
+        mIDP.addOnChangeListener(this);
+        StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
+    }
+
+    @Override
+    public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
+        // TODO: Implement caching and preloading
+        super.loadItems(ums, pinnedShortcuts);
+
+        WorkspaceItemFactory allAppsFactory =
+                new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numAllAppsColumns);
+        mAllAppsState.items.setItems(
+                mAllAppsState.storage.read(mApp.getContext(), allAppsFactory, ums.allUsers::get));
+        mDataModel.extraItems.put(CONTAINER_PREDICTION, mAllAppsState.items);
+
+        WorkspaceItemFactory hotseatFactory =
+                new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, mIDP.numHotseatIcons);
+        mHotseatState.items.setItems(
+                mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
+        mDataModel.extraItems.put(CONTAINER_HOTSEAT_PREDICTION, mHotseatState.items);
+        mActive = true;
+    }
+
+    @Override
+    public void workspaceLoadComplete() {
+        super.workspaceLoadComplete();
+        recreatePredictors();
+    }
+
+    @Override
+    public void validateData() {
+        super.validateData();
+        if (mAllAppsState.predictor != null) {
+            mAllAppsState.predictor.requestPredictionUpdate();
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+        mActive = false;
+        StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
+
+        destroyPredictors();
+        mIDP.removeOnChangeListener(this);
+    }
+
+    private void destroyPredictors() {
+        mAllAppsState.destroyPredictor();
+        mHotseatState.destroyPredictor();
+    }
+
+    @WorkerThread
+    private void recreatePredictors() {
+        destroyPredictors();
+        if (!mActive) {
+            return;
+        }
+        Context context = mApp.getContext();
+        AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+        if (apm == null) {
+            return;
+        }
+
+        registerPredictor(mAllAppsState, apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("home")
+                        .setPredictedTargetCount(mIDP.numAllAppsColumns)
+                        .build()));
+
+        // TODO: get bundle
+        registerPredictor(mHotseatState, apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(context)
+                        .setUiSurface("hotseat")
+                        .setPredictedTargetCount(mIDP.numHotseatIcons)
+                        .setExtras(convertDataModelToAppTargetBundle(context, mDataModel))
+                        .build()));
+
+    }
+
+    private void registerPredictor(PredictorState state, AppPredictor predictor) {
+        state.predictor = predictor;
+        state.predictor.registerPredictionUpdates(
+                Executors.MODEL_EXECUTOR, t -> handleUpdate(state, t));
+        state.predictor.requestPredictionUpdate();
+    }
+
+    private void handleUpdate(PredictorState state, List<AppTarget> targets) {
+        if (state.setTargets(targets)) {
+            // No diff, skip
+            return;
+        }
+        mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
+    }
+
+    @Override
+    public void onIdpChanged(int changeFlags, InvariantDeviceProfile profile) {
+        if ((changeFlags & CHANGE_FLAG_GRID) != 0) {
+            // Reinitialize everything
+            Executors.MODEL_EXECUTOR.execute(this::recreatePredictors);
+        }
+    }
+
+    private void onAppTargetEvent(AppTargetEvent event, int client) {
+        PredictorState state = client == CONTAINER_PREDICTION ? mAllAppsState : mHotseatState;
+        if (state.predictor != null) {
+            state.predictor.notifyAppTargetEvent(event);
+        }
+    }
+
+    static class PredictorState {
+
+        public final FixedContainerItems items;
+        public final PersistedItemArray storage;
+        public AppPredictor predictor;
+
+        private List<AppTarget> mLastTargets;
+
+        PredictorState(int container, String storageName) {
+            items = new FixedContainerItems(container);
+            storage = new PersistedItemArray(storageName);
+            mLastTargets = Collections.emptyList();
+        }
+
+        public void destroyPredictor() {
+            if (predictor != null) {
+                predictor.destroy();
+                predictor = null;
+            }
+        }
+
+        /**
+         * Sets the new targets and returns true if it was different than before.
+         */
+        boolean setTargets(List<AppTarget> newTargets) {
+            List<AppTarget> oldTargets = mLastTargets;
+            mLastTargets = newTargets;
+
+            int size = oldTargets.size();
+            return size == newTargets.size() && IntStream.range(0, size)
+                    .allMatch(i -> areAppTargetsSame(oldTargets.get(i), newTargets.get(i)));
+        }
+    }
+
+    /**
+     * Compares two targets for the properties which we care about
+     */
+    private static boolean areAppTargetsSame(AppTarget t1, AppTarget t2) {
+        if (!Objects.equals(t1.getPackageName(), t2.getPackageName())
+                || !Objects.equals(t1.getUser(), t2.getUser())
+                || !Objects.equals(t1.getClassName(), t2.getClassName())) {
+            return false;
+        }
+
+        ShortcutInfo s1 = t1.getShortcutInfo();
+        ShortcutInfo s2 = t2.getShortcutInfo();
+        if (s1 != null) {
+            if (s2 == null || !Objects.equals(s1.getId(), s2.getId())) {
+                return false;
+            }
+        } else if (s2 != null) {
+            return false;
+        }
+        return true;
+    }
+
+    private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory {
+
+        private final LauncherAppState mAppState;
+        private final UserManagerState mUMS;
+        private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
+        private final int mMaxCount;
+
+        private int mReadCount = 0;
+
+        protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
+                Map<ShortcutKey, ShortcutInfo> pinnedShortcuts, int maxCount) {
+            mAppState = appState;
+            mUMS = ums;
+            mPinnedShortcuts = pinnedShortcuts;
+            mMaxCount = maxCount;
+        }
+
+        @Nullable
+        @Override
+        public ItemInfo createInfo(int itemType, UserHandle user, Intent intent) {
+            if (mReadCount >= mMaxCount) {
+                return null;
+            }
+            switch (itemType) {
+                case ITEM_TYPE_APPLICATION: {
+                    LauncherActivityInfo lai = mAppState.getContext()
+                            .getSystemService(LauncherApps.class)
+                            .resolveActivity(intent, user);
+                    if (lai == null) {
+                        return null;
+                    }
+                    AppInfo info = new AppInfo(lai, user, mUMS.isUserQuiet(user));
+                    mAppState.getIconCache().getTitleAndIcon(info, lai, false);
+                    mReadCount++;
+                    return info.makeWorkspaceItem();
+                }
+                case ITEM_TYPE_DEEP_SHORTCUT: {
+                    ShortcutKey key = ShortcutKey.fromIntent(intent, user);
+                    if (key == null) {
+                        return null;
+                    }
+                    ShortcutInfo si = mPinnedShortcuts.get(key);
+                    if (si == null) {
+                        return null;
+                    }
+                    WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+                    mAppState.getIconCache().getShortcutIcon(wii, si);
+                    mReadCount++;
+                    return wii;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index f42b124..810f4e3 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -43,9 +43,15 @@
 import android.util.Log;
 
 import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.RemoteActionShortcut;
 import com.android.launcher3.popup.SystemShortcut;
@@ -57,6 +63,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * Data model for digital wellbeing status of apps.
@@ -71,13 +78,21 @@
     private static final int MSG_PACKAGE_REMOVED = 2;
     private static final int MSG_FULL_REFRESH = 3;
 
+    private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
+    private static final int IN_MINIMAL_DEVICE = 2;
+
     // Welbeing contract
+    private static final String PATH_ACTIONS = "actions";
+    private static final String PATH_MINIMAL_DEVICE = "minimal_device";
+    private static final String METHOD_GET_MINIMAL_DEVICE_CONFIG = "get_minimal_device_config";
     private static final String METHOD_GET_ACTIONS = "get_actions";
     private static final String EXTRA_ACTIONS = "actions";
     private static final String EXTRA_ACTION = "action";
     private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
     private static final String EXTRA_PACKAGES = "packages";
     private static final String EXTRA_SUCCESS = "success";
+    private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
+    private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
 
     public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
             new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -104,15 +119,23 @@
         mContentObserver = new ContentObserver(MAIN_EXECUTOR.getHandler()) {
             @Override
             public void onChange(boolean selfChange, Uri uri) {
-                // Wellbeing reports that app actions have changed.
                 if (DEBUG || mIsInTest) {
-                    Log.d(TAG, "ContentObserver.onChange() called with: selfChange = [" + selfChange
-                            + "], uri = [" + uri + "]");
+                    Log.d(TAG, "ContentObserver.onChange() called with: selfChange = ["
+                            + selfChange + "], uri = [" + uri + "]");
                 }
                 Preconditions.assertUIThread();
-                updateWellbeingData();
+
+                if (uri.getPath().contains(PATH_ACTIONS)) {
+                    // Wellbeing reports that app actions have changed.
+                    updateWellbeingData();
+                } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
+                    // Wellbeing reports that minimal device state or config is changed.
+                    updateLauncherModel(context);
+                }
             }
         };
+        FeatureFlags.ENABLE_MINIMAL_DEVICE.addChangeListener(mContext, () ->
+                updateLauncherModel(context));
 
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             context.registerReceiver(
@@ -146,10 +169,13 @@
     private void restartObserver() {
         final ContentResolver resolver = mContext.getContentResolver();
         resolver.unregisterContentObserver(mContentObserver);
-        Uri actionsUri = apiBuilder().path("actions").build();
+        Uri actionsUri = apiBuilder().path(PATH_ACTIONS).build();
+        Uri minimalDeviceUri = apiBuilder().path(PATH_MINIMAL_DEVICE).build();
         try {
             resolver.registerContentObserver(
                     actionsUri, true /* notifyForDescendants */, mContentObserver);
+            resolver.registerContentObserver(
+                    minimalDeviceUri, true /* notifyForDescendants */, mContentObserver);
         } catch (Exception e) {
             Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
             if (mIsInTest) throw new RuntimeException(e);
@@ -191,12 +217,69 @@
         mWorkerHandler.sendEmptyMessage(MSG_FULL_REFRESH);
     }
 
+    private void updateLauncherModel(@NonNull final Context context) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            reloadLauncherInNormalMode(context);
+            return;
+        }
+        runWithMinimalDeviceConfigs((bundle) -> {
+            if (bundle.getInt(EXTRA_MINIMAL_DEVICE_STATE, UNKNOWN_MINIMAL_DEVICE_STATE)
+                    == IN_MINIMAL_DEVICE) {
+                reloadLauncherInMinimalMode(context);
+            } else {
+                reloadLauncherInNormalMode(context);
+            }
+        });
+    }
+
+    private void reloadLauncherInNormalMode(@NonNull final Context context) {
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                InvariantDeviceProfile.INSTANCE.get(context).dbFile);
+    }
+
+    private void reloadLauncherInMinimalMode(@NonNull final Context context) {
+        final Bundle extras = new Bundle();
+        extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
+                mWellbeingProviderPkg + ".api");
+        LauncherSettings.Settings.call(context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
+                DB_NAME_MINIMAL_DEVICE, extras);
+    }
+
     private Uri.Builder apiBuilder() {
         return new Uri.Builder()
                 .scheme(SCHEME_CONTENT)
                 .authority(mWellbeingProviderPkg + ".api");
     }
 
+    /**
+     * Fetch most up-to-date minimal device config.
+     */
+    @WorkerThread
+    private void runWithMinimalDeviceConfigs(Consumer<Bundle> consumer) {
+        if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
+            return;
+        }
+        if (DEBUG || mIsInTest) {
+            Log.d(TAG, "runWithMinimalDeviceConfigs() called");
+        }
+        Preconditions.assertNonUiThread();
+
+        final Uri contentUri = apiBuilder().build();
+        final Bundle remoteBundle;
+        try (ContentProviderClient client = mContext.getContentResolver()
+                .acquireUnstableContentProviderClient(contentUri)) {
+            remoteBundle = client.call(
+                    METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */);
+            consumer.accept(remoteBundle);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
+            if (mIsInTest) throw new RuntimeException(e);
+        }
+        if (DEBUG || mIsInTest) Log.i(TAG, "runWithMinimalDeviceConfigs(): finished");
+    }
+
     private boolean updateActions(String... packageNames) {
         if (packageNames.length == 0) {
             return true;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index ec3a490..aad7e17 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -16,19 +16,22 @@
 
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.graphics.OverviewScrim.SCRIM_MULTIPLIER;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.util.FloatProperty;
 
@@ -61,12 +64,14 @@
     @Override
     public void setState(@NonNull LauncherState state) {
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
-        SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
+        RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
         ADJACENT_PAGE_OFFSET.set(mRecentsView, scaleAndOffset[1]);
+        TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
 
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
+        SCRIM_MULTIPLIER.set(scrim, 1f);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
     }
 
@@ -93,16 +98,20 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
         float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+        setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
         setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+        setter.setFloat(scrim, SCRIM_MULTIPLIER, 1f,
+                config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
 
         setter.setFloat(
                 mRecentsView, getTaskModalnessProperty(),
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
rename to quickstep/src/com/android/launcher3/uioverrides/PredictedAppIconInflater.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
similarity index 80%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
rename to quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f87138e..e808f8c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -17,6 +17,9 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
 
+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.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -27,34 +30,33 @@
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.res.Configuration;
-import android.os.Bundle;
 import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
+import com.android.launcher3.appprediction.PredictionRowView;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
-import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -65,21 +67,22 @@
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
-import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.launcher3.util.UiThreadHelper.AsyncCommand;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
-import java.util.OptionalInt;
+import java.util.Objects;
 import java.util.stream.Stream;
 
 public class QuickstepLauncher extends BaseQuickstepLauncher {
@@ -91,13 +94,8 @@
     public static final AsyncCommand SET_SHELF_HEIGHT = (context, arg1, arg2) ->
             SystemUiProxy.INSTANCE.get(context).setShelfHeight(arg1 != 0, arg2);
 
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.createPredictor();
-        }
-    }
+    private FixedContainerItems mAllAppsPredictions;
+    private HotseatPredictionController mHotseatPredictionController;
 
     @Override
     protected void setupViews() {
@@ -111,8 +109,23 @@
     protected void logAppLaunch(ItemInfo info, InstanceId instanceId) {
         StatsLogger logger = getStatsLogManager()
                 .logger().withItemInfo(info).withInstanceId(instanceId);
-        OptionalInt allAppsRank = PredictionUiStateManager.INSTANCE.get(this).getAllAppsRank(info);
-        allAppsRank.ifPresent(logger::withRank);
+
+        if (mAllAppsPredictions != null
+                && (info.itemType == ITEM_TYPE_APPLICATION
+                || info.itemType == ITEM_TYPE_SHORTCUT
+                || info.itemType == ITEM_TYPE_DEEP_SHORTCUT)) {
+            int count = mAllAppsPredictions.items.size();
+            for (int i = 0; i < count; i++) {
+                ItemInfo targetInfo = mAllAppsPredictions.items.get(i);
+                if (targetInfo.itemType == info.itemType
+                        && targetInfo.user.equals(info.user)
+                        && Objects.equals(targetInfo.getIntent(), info.getIntent())) {
+                    logger.withRank(i);
+                    break;
+                }
+
+            }
+        }
         logger.log(LAUNCHER_APP_LAUNCH_TAP);
 
         if (mHotseatPredictionController != null) {
@@ -120,6 +133,18 @@
         }
     }
 
+    /**
+     * Returns Prediction controller for hybrid hotseat
+     */
+    public HotseatPredictionController getHotseatPredictionController() {
+        return mHotseatPredictionController;
+    }
+
+    @Override
+    protected OnboardingPrefs createOnboardingPrefs(SharedPreferences sharedPrefs) {
+        return new QuickstepOnboardingPrefs(this, sharedPrefs);
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -149,19 +174,10 @@
     }
 
     @Override
-    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
-        super.folderCreatedFromItem(folder, itemInfo);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
-        }
-    }
-
-    @Override
-    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
-        super.folderConvertedToItem(folder, itemInfo);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
-        }
+    protected void showAllAppsFromIntent(boolean alreadyOnHome) {
+        ActivityManagerWrapper.getInstance().closeSystemWindows(
+            CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+        super.showAllAppsFromIntent(alreadyOnHome);
     }
 
     @Override
@@ -192,10 +208,14 @@
     }
 
     @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) {
-        super.bindPredictedItems(appInfos, ranks);
-        if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.showCachedItems(appInfos, ranks);
+    public void bindExtraContainerItems(FixedContainerItems item) {
+        if (item.containerId == Favorites.CONTAINER_PREDICTION) {
+            mAllAppsPredictions = item;
+            getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class)
+                    .setPredictedApps(item.items);
+        } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION
+                && mHotseatPredictionController != null) {
+            mHotseatPredictionController.setPredictedItems(item);
         }
     }
 
@@ -215,10 +235,8 @@
         switch (state.ordinal) {
             case HINT_STATE_ORDINAL: {
                 Workspace workspace = getWorkspace();
-                boolean willMoveScreens = workspace.getNextPage() != Workspace.DEFAULT_PAGE;
-                getStateManager().goToState(NORMAL, true,
-                        willMoveScreens ? null : getScrimView()::startDragHandleEducationAnim);
-                if (willMoveScreens) {
+                getStateManager().goToState(NORMAL);
+                if (workspace.getNextPage() != Workspace.DEFAULT_PAGE) {
                     workspace.post(workspace::moveToDefaultScreen);
                 }
                 break;
@@ -268,14 +286,7 @@
             if (TestProtocol.sDebugTracing) {
                 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.2");
             }
-            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.PAUSE_NOT_DETECTED, "createTouchControllers.3");
-                }
-                list.add(new NoButtonNavbarToOverviewTouchController(this));
-            } else {
-                list.add(new FlingAndHoldTouchController(this));
-            }
+            list.add(new NoButtonNavbarToOverviewTouchController(this));
         } else {
             if (getDeviceProfile().isVerticalBarLayout()) {
                 list.add(new OverviewToAllAppsTouchController(this));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
similarity index 88%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
rename to quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 085b9b3..5ccc1e8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
@@ -57,7 +58,7 @@
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
         }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state);
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
@@ -75,17 +76,19 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        setAlphas(builder, toState);
+        setAlphas(builder, config, toState);
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
-    private void setAlphas(PropertySetter propertySetter, LauncherState state) {
+    private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
+            LauncherState state) {
         float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
                 buttonAlpha, LINEAR);
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
-                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+                MultiValueAlpha.VALUE, buttonAlpha, config.getInterpolator(
+                        ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index e7cd393..bce73cd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,8 +16,7 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
 
@@ -25,7 +24,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.quickstep.SysUINavigationMode;
 
 /**
  * Definition for AllApps state
@@ -65,13 +63,7 @@
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
         ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
                 .getWorkspaceScaleAndTranslation(launcher);
-        if (SysUINavigationMode.getMode(launcher) == NO_BUTTON && !ENABLE_OVERVIEW_ACTIONS.get()) {
-            float normalScale = 1;
-            // Scale down halfway to where we'd be in overview, to prepare for a potential pause.
-            scaleAndTranslation.scale = (scaleAndTranslation.scale + normalScale) / 2;
-        } else {
-            scaleAndTranslation.scale = 1;
-        }
+        scaleAndTranslation.scale = 1;
         return scaleAndTranslation;
     }
 
@@ -92,7 +84,8 @@
 
     @Override
     public float[] getOverviewScaleAndOffset(Launcher launcher) {
-        return new float[] {0.9f, 0};
+        float offset = removeShelfFromOverview(launcher) ? 1 : 0;
+        return new float[] {0.9f, offset};
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index d174bfd..bbe7821 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -31,7 +29,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.LayoutUtils;
@@ -65,7 +62,7 @@
     public int getTransitionDuration(Context context) {
         // In no-button mode, overview comes in all the way from the left, so give it more time.
         boolean isNoButtonMode = SysUINavigationMode.INSTANCE.get(context).getMode() == NO_BUTTON;
-        return isNoButtonMode && ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
+        return isNoButtonMode ? 380 : 250;
     }
 
     @Override
@@ -108,8 +105,7 @@
 
     @Override
     public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()
-                && removeShelfFromOverview(launcher)) {
+        if (this == OVERVIEW && removeShelfFromOverview(launcher)) {
             // Treat the QSB as part of the hotseat so they move together.
             return getHotseatScaleAndTranslation(launcher);
         }
@@ -129,7 +125,7 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher) ||
+        if (removeShelfFromOverview(launcher) ||
                 hideShelfInTwoButtonLandscape(launcher, recentsView.getPagedOrientationHandler())) {
             return OVERVIEW_BUTTONS;
         } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
@@ -179,8 +175,6 @@
     public void onBackPressed(Launcher launcher) {
         TaskView taskView = launcher.<RecentsView>getOverviewPanel().getRunningTaskView();
         if (taskView != null) {
-            launcher.getUserEventDispatcher().logActionCommand(Action.Command.BACK,
-                    newContainerTarget(ContainerType.OVERVIEW));
             taskView.launchTask(true);
         } else {
             super.onBackPressed(launcher);
@@ -191,10 +185,6 @@
         return new BackgroundAppState(id);
     }
 
-    public static OverviewState newPeekState(int id) {
-        return new OverviewPeekState(id);
-    }
-
     public static OverviewState newSwitchState(int id) {
         return new QuickSwitchState(id);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
similarity index 75%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
rename to quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index a0af797..77fd103 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -17,41 +17,35 @@
 
 import static android.view.View.VISIBLE;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 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.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 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_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
 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_SCRIM_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -67,7 +61,6 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.quickstep.SysUINavigationMode;
@@ -84,10 +77,8 @@
     private static final float RECENTS_PREPARE_SCALE = 1.33f;
 
     public static final int INDEX_SHELF_ANIM = RecentsAtomicAnimationFactory.NEXT_INDEX + 0;
-    public static final int INDEX_PAUSE_TO_OVERVIEW_ANIM =
-            RecentsAtomicAnimationFactory.NEXT_INDEX + 1;
 
-    private static final int MY_ANIM_COUNT = 2;
+    private static final int MY_ANIM_COUNT = 1;
     protected static final int NEXT_INDEX = RecentsAtomicAnimationFactory.NEXT_INDEX
             + MY_ANIM_COUNT;
 
@@ -95,8 +86,6 @@
     // cache the value.
     private int mHintToNormalDuration = -1;
 
-    public static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
-
     public QuickstepAtomicAnimationFactory(QuickstepLauncher activity) {
         super(activity, MY_ANIM_COUNT);
     }
@@ -135,21 +124,6 @@
 
                 return springAnim;
             }
-            case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
-                StateAnimationConfig config = new StateAnimationConfig();
-                config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
-
-                config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-                config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
-                if ((OVERVIEW.getVisibleElements(mActivity) & HOTSEAT_ICONS) != 0) {
-                    config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
-                    config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
-                }
-
-                StateManager<LauncherState> stateManager = mActivity.getStateManager();
-                return stateManager.createAtomicAnimation(
-                        stateManager.getCurrentStableState(), OVERVIEW, config);
-            }
             default:
                 return super.createStateElementAnimation(index, values);
         }
@@ -163,10 +137,15 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
             config.setInterpolator(ANIM_ALL_APPS_FADE, ACCEL);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
-            Workspace workspace = mActivity.getWorkspace();
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL_DEACCEL);
 
+            if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
+                config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
+            } else {
+                config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+            }
+
+            Workspace workspace = mActivity.getWorkspace();
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
             boolean isWorkspaceVisible = workspace.getVisibility() == VISIBLE;
             if (isWorkspaceVisible) {
@@ -184,48 +163,39 @@
             if (!isHotseatVisible) {
                 hotseat.setScaleX(0.92f);
                 hotseat.setScaleY(0.92f);
-                if (ENABLE_OVERVIEW_ACTIONS.get()) {
-                    AllAppsContainerView qsbContainer = mActivity.getAppsView();
-                    View qsb = qsbContainer.getSearchView();
-                    boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
-                    if (!qsbVisible) {
-                        qsbContainer.setScaleX(0.92f);
-                        qsbContainer.setScaleY(0.92f);
-                    }
+                AllAppsContainerView qsbContainer = mActivity.getAppsView();
+                View qsb = qsbContainer.getSearchView();
+                boolean qsbVisible = qsb.getVisibility() == VISIBLE && qsb.getAlpha() > 0;
+                if (!qsbVisible) {
+                    qsbContainer.setScaleX(0.92f);
+                    qsbContainer.setScaleY(0.92f);
                 }
             }
-        } else if (toState == NORMAL && fromState == OVERVIEW_PEEK) {
-            // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
-        } else if (toState == OVERVIEW_PEEK && fromState == NORMAL) {
-            config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
-            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
-            config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
         } else if ((fromState == NORMAL || fromState == HINT_STATE) && toState == OVERVIEW) {
             if (SysUINavigationMode.getMode(mActivity) == NO_BUTTON) {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE,
                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+                config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
             } else {
                 config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
 
                 // Scale up the recents, if it is not coming from the side
                 RecentsView overview = mActivity.getOverviewPanel();
                 if (overview.getVisibility() != VISIBLE || overview.getContentAlpha() == 0) {
-                    SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
+                    RECENTS_SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
                 }
             }
             config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_ALL_APPS_FADE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             config.setInterpolator(ANIM_DEPTH, OVERSHOOT_1_2);
-            Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
-                    && removeShelfFromOverview(mActivity)
+            Interpolator translationInterpolator = removeShelfFromOverview(mActivity)
                     ? OVERSHOOT_1_2
                     : OVERSHOOT_1_7;
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
             config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
-            config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
         } else if (fromState == HINT_STATE && toState == NORMAL) {
             config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
             if (mHintToNormalDuration == -1) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
similarity index 89%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index c1a585e..7616844 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -25,7 +25,6 @@
 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.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -45,6 +44,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.testing.TestProtocol;
@@ -52,7 +52,9 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.AssistantUtilities;
+import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -63,6 +65,8 @@
         SingleAxisSwipeDetector.Listener {
 
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL_3;
+    // The min amount of overview scrim we keep during the transition.
+    private static final float OVERVIEW_TO_HOME_SCRIM_MULTIPLIER = 0.5f;
 
     private final Launcher mLauncher;
     private final SingleAxisSwipeDetector mSwipeDetector;
@@ -156,12 +160,18 @@
         final PendingAnimation builder = new PendingAnimation(accuracy);
         if (mStartState.overviewUi) {
             RecentsView recentsView = mLauncher.getOverviewPanel();
-            builder.setFloat(recentsView, ADJACENT_PAGE_OFFSET,
-                    -mPullbackDistance / recentsView.getPageOffsetScale(), PULLBACK_INTERPOLATOR);
+            AnimatorControllerWithResistance.createRecentsResistanceFromOverviewAnim(mLauncher,
+                    builder);
+
+            builder.setFloat(mLauncher.getDragLayer().getOverviewScrim(),
+                    OverviewScrim.SCRIM_MULTIPLIER, OVERVIEW_TO_HOME_SCRIM_MULTIPLIER,
+                    PULLBACK_INTERPOLATOR);
+
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                builder.addOnFrameCallback(
-                        () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+                builder.addOnFrameCallback(recentsView::redrawLiveTile);
             }
+
+            AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_TASK_MENU);
         } else if (mStartState == ALL_APPS) {
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
             builder.setFloat(allAppsController, ALL_APPS_PROGRESS,
@@ -212,15 +222,20 @@
                 recentsView.switchToScreenshot(null,
                         () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
             }
-            mLauncher.getStateManager().goToState(mEndState, true,
-                    () -> onSwipeInteractionCompleted(mEndState));
+            if (mStartState.overviewUi) {
+                new OverviewToHomeAnim(mLauncher, () -> onSwipeInteractionCompleted(mEndState))
+                        .animateWithVelocity(velocity);
+            } else {
+                mLauncher.getStateManager().goToState(mEndState, true,
+                        () -> onSwipeInteractionCompleted(mEndState));
+            }
             if (mStartState != mEndState) {
-                logStateChange(mStartState.containerType, logAction);
+                // TODO: add to WW log
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
             if (topOpenView != null) {
                 AbstractFloatingView.closeAllOpenViews(mLauncher);
-                logStateChange(topOpenView.getLogContainerType(), logAction);
+                // TODO: add to WW log
             }
             ActivityManagerWrapper.getInstance()
                     .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
new file mode 100644
index 0000000..c78d474
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
+
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.MotionPauseDetector;
+import com.android.quickstep.util.OverviewToHomeAnim;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
+ * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
+ * first home screen instead of to Overview.
+ */
+public class NoButtonNavbarToOverviewTouchController extends PortraitStatesTouchController {
+
+
+    // How much of the movement to use for translating overview after swipe and hold.
+    private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
+    private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
+    private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+
+    private final RecentsView mRecentsView;
+    private final MotionPauseDetector mMotionPauseDetector;
+    private final float mMotionPauseMinDisplacement;
+
+    private boolean mDidTouchStartInNavBar;
+    private boolean mStartedOverview;
+    private boolean mReachedOverview;
+    // The last recorded displacement before we reached overview.
+    private PointF mStartDisplacement = new PointF();
+    private float mStartY;
+    private AnimatorPlaybackController mOverviewResistYAnim;
+
+    // Normal to Hint animation has flag SKIP_OVERVIEW, so we update this scrim with this animator.
+    private ObjectAnimator mNormalToHintOverviewScrimAnimator;
+
+    public NoButtonNavbarToOverviewTouchController(Launcher l) {
+        super(l, false /* allowDragToOverview */);
+        mRecentsView = l.getOverviewPanel();
+        mMotionPauseDetector = new MotionPauseDetector(l);
+        mMotionPauseMinDisplacement = ViewConfiguration.get(l).getScaledTouchSlop();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController.ctor");
+        }
+    }
+
+    @Override
+    protected boolean canInterceptTouch(MotionEvent ev) {
+        mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+        return super.canInterceptTouch(ev);
+    }
+
+    @Override
+    protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+        if (fromState == NORMAL && mDidTouchStartInNavBar) {
+            return HINT_STATE;
+        } else if (fromState == OVERVIEW && isDragTowardPositive) {
+            // Don't allow swiping up to all apps.
+            return OVERVIEW;
+        }
+        return super.getTargetState(fromState, isDragTowardPositive);
+    }
+
+    @Override
+    protected float initCurrentAnimation(int animComponents) {
+        float progressMultiplier = super.initCurrentAnimation(animComponents);
+        if (mToState == HINT_STATE) {
+            // Track the drag across the entire height of the screen.
+            progressMultiplier = -1 / getShiftRange();
+        }
+        return progressMultiplier;
+    }
+
+    @Override
+    public void onDragStart(boolean start, float startDisplacement) {
+        super.onDragStart(start, startDisplacement);
+
+        mMotionPauseDetector.clear();
+
+        if (handlingOverviewAnim()) {
+            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
+        }
+
+        if (mFromState == NORMAL && mToState == HINT_STATE) {
+            mNormalToHintOverviewScrimAnimator = ObjectAnimator.ofFloat(
+                    mLauncher.getDragLayer().getOverviewScrim(),
+                    OverviewScrim.SCRIM_PROGRESS,
+                    mFromState.getOverviewScrimAlpha(mLauncher),
+                    mToState.getOverviewScrimAlpha(mLauncher));
+        }
+        mStartedOverview = false;
+        mReachedOverview = false;
+        mOverviewResistYAnim = null;
+    }
+
+    @Override
+    protected void updateProgress(float fraction) {
+        super.updateProgress(fraction);
+        if (mNormalToHintOverviewScrimAnimator != null) {
+            mNormalToHintOverviewScrimAnimator.setCurrentFraction(fraction);
+        }
+    }
+
+    @Override
+    public void onDragEnd(float velocity) {
+        if (mStartedOverview) {
+            goToOverviewOrHomeOnDragEnd(velocity);
+        } else {
+            super.onDragEnd(velocity);
+        }
+
+        View searchView = mLauncher.getAppsView().getSearchView();
+        if (searchView instanceof FeedbackHandler) {
+            ((FeedbackHandler) searchView).resetFeedback();
+        }
+
+        mMotionPauseDetector.clear();
+        mNormalToHintOverviewScrimAnimator = null;
+        if (mLauncher.isInState(OVERVIEW)) {
+            // Normally we would cleanup the state based on mCurrentAnimation, but since we stop
+            // using that when we pause to go to Overview, we need to clean up ourselves.
+            clearState();
+        }
+    }
+
+    @Override
+    protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+            LauncherState targetState, float velocity, boolean isFling) {
+        super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
+                isFling);
+        if (targetState == HINT_STATE) {
+            // Normally we compute the duration based on the velocity and distance to the given
+            // state, but since the hint state tracks the entire screen without a clear endpoint, we
+            // need to manually set the duration to a reasonable value.
+            animator.setDuration(HINT_STATE.getTransitionDuration(mLauncher));
+        }
+    }
+
+    private void onMotionPauseDetected() {
+        if (mCurrentAnimation == null) {
+            return;
+        }
+        mNormalToHintOverviewScrimAnimator = null;
+        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+            mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+                mOverviewResistYAnim = AnimatorControllerWithResistance
+                        .createRecentsResistanceFromOverviewAnim(mLauncher, null)
+                        .createPlaybackController();
+                mReachedOverview = true;
+                maybeSwipeInteractionToOverviewComplete();
+            });
+        });
+        mStartedOverview = true;
+        VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    private void maybeSwipeInteractionToOverviewComplete() {
+        if (mReachedOverview && mDetector.isSettlingState()) {
+            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+        }
+    }
+
+    private boolean handlingOverviewAnim() {
+        int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
+        return mDidTouchStartInNavBar && mStartState == NORMAL
+                && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+    }
+
+    @Override
+    public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "NoButtonNavbarToOverviewTouchController");
+        }
+        if (mStartedOverview) {
+            if (!mReachedOverview) {
+                mStartDisplacement.set(xDisplacement, yDisplacement);
+                mStartY = event.getY();
+            } else {
+                mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
+                        * OVERVIEW_MOVEMENT_FACTOR);
+                float yProgress = (mStartDisplacement.y - yDisplacement) / mStartY;
+                if (yProgress > 0 && mOverviewResistYAnim != null) {
+                    mOverviewResistYAnim.setPlayFraction(yProgress);
+                } else {
+                    mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+                            * OVERVIEW_MOVEMENT_FACTOR);
+                }
+            }
+        }
+
+        float upDisplacement = -yDisplacement;
+        mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
+                || upDisplacement < mMotionPauseMinDisplacement);
+        mMotionPauseDetector.addPosition(event);
+
+        // Stay in Overview.
+        return mStartedOverview || super.onDrag(yDisplacement, xDisplacement, event);
+    }
+
+    private void goToOverviewOrHomeOnDragEnd(float velocity) {
+        boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
+        if (goToHomeInsteadOfOverview) {
+            new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING))
+                    .animateWithVelocity(velocity);
+        }
+        if (mReachedOverview) {
+            float distanceDp = dpiFromPx(Math.max(
+                    Math.abs(mRecentsView.getTranslationX()),
+                    Math.abs(mRecentsView.getTranslationY())));
+            long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+                    distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+            mRecentsView.animate()
+                    .translationX(0)
+                    .translationY(0)
+                    .setInterpolator(ACCEL_DEACCEL)
+                    .setDuration(duration)
+                    .withEndAction(goToHomeInsteadOfOverview
+                            ? null
+                            : this::maybeSwipeInteractionToOverviewComplete);
+            if (!goToHomeInsteadOfOverview) {
+                // Return to normal properties for the overview state.
+                StateAnimationConfig config = new StateAnimationConfig();
+                config.duration = duration;
+                LauncherState state = mLauncher.getStateManager().getState();
+                mLauncher.getStateManager().createAtomicAnimation(state, state, config).start();
+            }
+        }
+    }
+
+    private float dpiFromPx(float pixels) {
+        return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
+    }
+
+    @Override
+    protected StateAnimationConfig getConfigForStates(
+            LauncherState fromState, LauncherState toState) {
+        if (fromState == NORMAL && toState == ALL_APPS) {
+            StateAnimationConfig builder = new StateAnimationConfig();
+            // Fade in prediction icons quickly, then rest of all apps after reaching overview.
+            float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
+                    - OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    0,
+                    ALL_APPS_CONTENT_FADE_THRESHOLD));
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    ACCEL,
+                    progressToReachOverview,
+                    progressToReachOverview + ALL_APPS_CONTENT_FADE_THRESHOLD));
+
+            // Get workspace out of the way quickly, to prepare for potential pause.
+            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, DEACCEL_3);
+            builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
+            return builder;
+        } else if (fromState == ALL_APPS && toState == NORMAL) {
+            StateAnimationConfig builder = new StateAnimationConfig();
+            // Keep all apps/predictions opaque until the very end of the transition.
+            float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
+            builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    progressToReachOverview - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    progressToReachOverview));
+            builder.setInterpolator(ANIM_ALL_APPS_HEADER_FADE, Interpolators.clampToProgress(
+                    DEACCEL,
+                    1 - ALL_APPS_CONTENT_FADE_THRESHOLD,
+                    1));
+            return builder;
+        }
+        return super.getConfigForStates(fromState, toState);
+    }
+
+    /**
+     * Interface for views with feedback animation requiring reset
+     */
+    public interface FeedbackHandler {
+
+        /**
+         * reset searchWidget feedback
+         */
+        void resetFeedback();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
similarity index 81%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 1b439d1..a6a3497 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,19 +15,15 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-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.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
@@ -39,14 +35,12 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
-import static com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory.INDEX_PAUSE_TO_OVERVIEW_ANIM;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.CANCEL;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
@@ -60,10 +54,8 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -74,11 +66,11 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.MotionPauseDetector;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.quickstep.views.LauncherRecentsView;
 
@@ -87,19 +79,20 @@
  * the user as possible, also handles swipe up and hold to go to overview and swiping back home.
  */
 public class NoButtonQuickSwitchTouchController implements TouchController,
-        BothAxesSwipeDetector.Listener, MotionPauseDetector.OnMotionPauseListener {
+        BothAxesSwipeDetector.Listener {
 
     /** The minimum progress of the scale/translationY animation until drag end. */
-    private static final float Y_ANIM_MIN_PROGRESS = 0.15f;
+    private static final float Y_ANIM_MIN_PROGRESS = 0.25f;
     private static final Interpolator FADE_OUT_INTERPOLATOR = DEACCEL_5;
     private static final Interpolator TRANSLATE_OUT_INTERPOLATOR = ACCEL_0_75;
-    private static final Interpolator SCALE_DOWN_INTERPOLATOR = DEACCEL;
+    private static final Interpolator SCALE_DOWN_INTERPOLATOR = LINEAR;
+    private static final long ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW = 300;
 
     private final BaseQuickstepLauncher mLauncher;
     private final BothAxesSwipeDetector mSwipeDetector;
-    private final ShelfPeekAnim mShelfPeekAnim;
     private final float mXRange;
     private final float mYRange;
+    private final float mMaxYProgress;
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
     private final LauncherRecentsView mRecentsView;
@@ -113,16 +106,16 @@
     // and the other two to set overview properties based on x and y progress.
     private AnimatorPlaybackController mNonOverviewAnim;
     private AnimatorPlaybackController mXOverviewAnim;
-    private AnimatorPlaybackController mYOverviewAnim;
+    private AnimatedFloat mYOverviewAnim;
 
     public NoButtonQuickSwitchTouchController(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
         mSwipeDetector = new BothAxesSwipeDetector(mLauncher, this);
-        mShelfPeekAnim = mLauncher.getShelfPeekAnim();
         mRecentsView = mLauncher.getOverviewPanel();
         mXRange = mLauncher.getDeviceProfile().widthPx / 2f;
         mYRange = LayoutUtils.getShelfTrackingDistance(
             mLauncher, mLauncher.getDeviceProfile(), mRecentsView.getPagedOrientationHandler());
+        mMaxYProgress = mLauncher.getDeviceProfile().heightPx / mYRange;
         mMotionPauseDetector = new MotionPauseDetector(mLauncher);
         mMotionPauseMinDisplacement = mLauncher.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
@@ -174,7 +167,7 @@
         if (start) {
             mStartState = mLauncher.getStateManager().getState();
 
-            mMotionPauseDetector.setOnMotionPauseListener(this);
+            mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
 
             // We have detected horizontal drag start, now allow swipe up as well.
             mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT | DIRECTION_UP,
@@ -184,28 +177,8 @@
         }
     }
 
-    @Override
-    public void onMotionPauseChanged(boolean isPaused) {
+    private void onMotionPauseDetected() {
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
-        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            return;
-        }
-
-        ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
-        if (shelfState == PEEK) {
-            // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
-            AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            allAppsController.setAlphas(
-                    NORMAL, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
-
-            if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                // Hotseat was hidden, but we need it visible when peeking.
-                mLauncher.getHotseat().setAlpha(1);
-            }
-        }
-        mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
-                ShelfPeekAnim.DURATION);
     }
 
     private void setupAnimators() {
@@ -244,7 +217,7 @@
         final LauncherState toState = OVERVIEW;
 
         // Set RecentView's initial properties.
-        SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
+        RECENTS_SCALE_PROPERTY.set(mRecentsView, fromState.getOverviewScaleAndOffset(mLauncher)[0]);
         ADJACENT_PAGE_OFFSET.set(mRecentsView, 1f);
         mRecentsView.setContentAlpha(1);
         mRecentsView.setFullscreenProgress(fromState.getOverviewFullscreenProgress());
@@ -266,11 +239,22 @@
         //   - RecentsView scale
         //   - RecentsView fullscreenProgress
         PendingAnimation yAnim = new PendingAnimation((long) (mYRange * 2));
-        yAnim.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0], SCALE_DOWN_INTERPOLATOR);
+        yAnim.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
+                SCALE_DOWN_INTERPOLATOR);
         yAnim.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), SCALE_DOWN_INTERPOLATOR);
-        mYOverviewAnim = yAnim.createPlaybackController();
-        mYOverviewAnim.dispatchOnStart();
+        AnimatorPlaybackController yNormalController = yAnim.createPlaybackController();
+        AnimatorControllerWithResistance yAnimWithResistance = AnimatorControllerWithResistance
+                .createForRecents(yNormalController, mLauncher,
+                        mRecentsView.getPagedViewOrientedState(), mLauncher.getDeviceProfile(),
+                        mRecentsView, RECENTS_SCALE_PROPERTY, mRecentsView,
+                        TASK_SECONDARY_TRANSLATION);
+        mYOverviewAnim = new AnimatedFloat(() -> {
+            if (mYOverviewAnim != null) {
+                yAnimWithResistance.setProgress(mYOverviewAnim.value, mMaxYProgress);
+            }
+        });
+        yNormalController.dispatchOnStart();
     }
 
     @Override
@@ -286,27 +270,14 @@
         mIsHomeScreenVisible = FADE_OUT_INTERPOLATOR.getInterpolation(xProgress)
                 <= 1 - ALPHA_CUTOFF_THRESHOLD;
 
-        if (wasHomeScreenVisible && !mIsHomeScreenVisible) {
-            // Get the shelf all the way offscreen so it pops up when we decide to peek it.
-            mShelfPeekAnim.setShelfState(HIDE, LINEAR, 0);
-        }
-
-        // Only allow motion pause if the home screen is invisible, since some
-        // home screen elements will appear in the shelf on motion pause.
-        mMotionPauseDetector.setDisallowPause(mIsHomeScreenVisible
-                || -displacement.y < mMotionPauseMinDisplacement);
+        mMotionPauseDetector.setDisallowPause(-displacement.y < mMotionPauseMinDisplacement);
         mMotionPauseDetector.addPosition(ev);
 
-        if (mIsHomeScreenVisible) {
-            // Cancel the shelf anim so it doesn't clobber mNonOverviewAnim.
-            mShelfPeekAnim.setShelfState(CANCEL, LINEAR, 0);
-        }
-
         if (mXOverviewAnim != null) {
             mXOverviewAnim.setPlayFraction(xProgress);
         }
         if (mYOverviewAnim != null) {
-            mYOverviewAnim.setPlayFraction(yProgress);
+            mYOverviewAnim.updateValue(yProgress);
         }
         return true;
     }
@@ -320,8 +291,10 @@
         if (mMotionPauseDetector.isPaused() && noFling) {
             cancelAnimations();
 
-            Animator overviewAnim = mLauncher.createAtomicAnimationFactory()
-                    .createStateElementAnimation(INDEX_PAUSE_TO_OVERVIEW_ANIM);
+            StateAnimationConfig config = new StateAnimationConfig();
+            config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+            Animator overviewAnim = mLauncher.getStateManager().createAtomicAnimation(
+                    mStartState, OVERVIEW, config);
             overviewAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -353,9 +326,11 @@
         } else if (verticalFling) {
             targetState = velocity.y > 0 ? QUICK_SWITCH : NORMAL;
         } else {
-            // If user isn't flinging, just snap to the closest state based on x progress.
+            // If user isn't flinging, just snap to the closest state.
             boolean passedHorizontalThreshold = mXOverviewAnim.getInterpolatedProgress() > 0.5f;
-            targetState = passedHorizontalThreshold ? QUICK_SWITCH : NORMAL;
+            boolean passedVerticalThreshold = mYOverviewAnim.value > 1f;
+            targetState = passedHorizontalThreshold && !passedVerticalThreshold
+                    ? QUICK_SWITCH : NORMAL;
         }
 
         // Animate the various components to the target state.
@@ -374,9 +349,9 @@
 
         boolean flingUpToNormal = verticalFling && velocity.y < 0 && targetState == NORMAL;
 
-        float yProgress = mYOverviewAnim.getProgressFraction();
+        float yProgress = mYOverviewAnim.value;
         float startYProgress = Utilities.boundToRange(yProgress
-                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, 1f);
+                - velocity.y * getSingleFrameMs(mLauncher) / mYRange, 0f, mMaxYProgress);
         final float endYProgress;
         if (flingUpToNormal) {
             endYProgress = 1;
@@ -386,12 +361,11 @@
         } else {
             endYProgress = 0;
         }
-        long yDuration = BaseSwipeDetector.calculateDuration(velocity.y,
-                Math.abs(endYProgress - startYProgress));
-        ValueAnimator yOverviewAnim = mYOverviewAnim.getAnimationPlayer();
-        yOverviewAnim.setFloatValues(startYProgress, endYProgress);
+        float yDistanceToCover = Math.abs(endYProgress - startYProgress) * mYRange;
+        long yDuration = (long) (yDistanceToCover / Math.max(1f, Math.abs(velocity.y)));
+        ValueAnimator yOverviewAnim = mYOverviewAnim.animateToValue(startYProgress, endYProgress);
         yOverviewAnim.setDuration(yDuration);
-        mYOverviewAnim.dispatchOnStart();
+        mYOverviewAnim.updateValue(startYProgress);
 
         ValueAnimator nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
         if (flingUpToNormal && !mIsHomeScreenVisible) {
@@ -456,9 +430,8 @@
             mXOverviewAnim.getAnimationPlayer().cancel();
         }
         if (mYOverviewAnim != null) {
-            mYOverviewAnim.getAnimationPlayer().cancel();
+            mYOverviewAnim.cancelAnimation();
         }
-        mShelfPeekAnim.setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
         mMotionPauseDetector.clear();
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 20ee61d..059a703 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -90,13 +89,16 @@
 
     @Override
     protected boolean canInterceptTouch(MotionEvent ev) {
+        // If we are swiping to all apps instead of overview, allow it from anywhere.
+        boolean interceptAnywhere = mLauncher.isInState(NORMAL) && !mAllowDragToOverview;
         if (mCurrentAnimation != null) {
             if (mFinishFastOnSecondTouch) {
                 mCurrentAnimation.getAnimationPlayer().end();
             }
 
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()) {
+            if (ev.getY() >= allAppsController.getShiftRange() * allAppsController.getProgress()
+                    || interceptAnywhere) {
                 // If we are already animating from a previous state, we can intercept as long as
                 // the touch is below the current all apps progress (to allow for double swipe).
                 return true;
@@ -118,9 +120,7 @@
                 return false;
             }
         } else {
-            // If we are swiping to all apps instead of overview, allow it from anywhere.
-            boolean interceptAnywhere = mLauncher.isInState(NORMAL) && !mAllowDragToOverview;
-            // For all other states, only listen if the event originated below the hotseat height
+            // For non-normal states, only listen if the event originated below the hotseat height
             if (!interceptAnywhere && !isTouchOverHotseat(mLauncher, ev)) {
                 return false;
             }
@@ -142,6 +142,10 @@
                 Log.d(TestProtocol.OVERIEW_NOT_ALLAPPS,
                         "PortraitStatesTouchController.getTargetState 1");
             }
+            if (removeShelfFromOverview(mLauncher)) {
+                // Don't allow swiping down to overview.
+                return NORMAL;
+            }
             return TouchInteractionService.isConnected() ?
                     mLauncher.getStateManager().getLastState() : NORMAL;
         } else if (fromState == OVERVIEW) {
@@ -150,7 +154,7 @@
                         "PortraitStatesTouchController.getTargetState 2");
             }
             LauncherState positiveDragTarget = ALL_APPS;
-            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(mLauncher)) {
+            if (removeShelfFromOverview(mLauncher)) {
                 // Don't allow swiping up to all apps.
                 positiveDragTarget = OVERVIEW;
             }
@@ -245,7 +249,7 @@
 
         final StateAnimationConfig config = totalShift == 0 ? new StateAnimationConfig()
                 : getConfigForStates(mFromState, mToState);
-        config.animFlags = updateAnimComponentsOnReinit(animFlags);
+        config.animFlags = animFlags;
         config.duration = maxAccuracy;
 
         cancelPendingAnim();
@@ -279,14 +283,6 @@
         return 1 / totalShift;
     }
 
-    /**
-     * Give subclasses the chance to update the animation when we re-initialize towards a new state.
-     */
-    @AnimationFlags
-    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
-        return animComponents;
-    }
-
     private void cancelPendingAnim() {
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 0ee5d04..df6194d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
@@ -25,6 +24,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
@@ -82,7 +82,15 @@
         mDetector = new SingleAxisSwipeDetector(activity, this, dir);
     }
 
-    private boolean canInterceptTouch() {
+    private boolean canInterceptTouch(MotionEvent ev) {
+        if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0) {
+            // Don't intercept swipes on the nav bar, as user might be trying to go home
+            // during a task dismiss animation.
+            if (mCurrentAnimation != null) {
+                mCurrentAnimation.getAnimationPlayer().end();
+            }
+            return false;
+        }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.forceFinishIfCloseToEnd();
         }
@@ -118,7 +126,7 @@
             clearState();
         }
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mNoIntercept = !canInterceptTouch();
+            mNoIntercept = !canInterceptTouch(ev);
             if (mNoIntercept) {
                 return false;
             }
@@ -206,14 +214,19 @@
         long maxDuration = 2 * secondaryLayerDimension;
         int verticalFactor = orientationHandler.getTaskDragDisplacementFactor(mIsRtl);
         int secondaryTaskDimension = orientationHandler.getSecondaryDimension(mTaskBeingDragged);
+        // The interpolator controlling the most prominent visual movement. We use this to determine
+        // whether we passed SUCCESS_TRANSITION_PROGRESS.
+        final Interpolator currentInterpolator;
         if (goingUp) {
+            currentInterpolator = Interpolators.LINEAR;
             mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
+            currentInterpolator = Interpolators.ZOOM_IN;
             mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
-                    mTaskBeingDragged, maxDuration, Interpolators.ZOOM_IN);
+                    mTaskBeingDragged, maxDuration, currentInterpolator);
 
             // Since the thumbnail is what is filling the screen, based the end displacement on it.
             View thumbnailView = mTaskBeingDragged.getThumbnail();
@@ -228,6 +241,9 @@
         }
         mCurrentAnimation = mPendingAnimation.createPlaybackController()
                 .setOnCancelRunnable(this::clearState);
+        // Setting this interpolator doesn't affect the visual motion, but is used to determine
+        // whether we successfully reached the target state in onDragEnd().
+        mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
         onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
@@ -262,11 +278,6 @@
         mCurrentAnimation.setPlayFraction(Utilities.boundToRange(
                 totalDisplacement * mProgressMultiplier, 0, 1));
 
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsView.getCurrentPage() != 0 || isGoingUp) {
-                mRecentsView.redrawLiveTile(true);
-            }
-        }
         return true;
     }
 
@@ -297,13 +308,6 @@
         }
 
         mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
-                if (mRecentsView.getCurrentPage() != 0 || mCurrentAnimationIsGoingUp) {
-                    mRecentsView.redrawLiveTile(true);
-                }
-            });
-        }
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity, mEndDisplacement, animationDuration);
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TransposedQuickSwitchTouchController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
similarity index 77%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
rename to quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 27d4846..26ad377 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -15,21 +15,24 @@
  */
 package com.android.quickstep;
 
+import static android.widget.Toast.LENGTH_SHORT;
+
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.QuickstepAppTransitionManagerImpl.TRANSITION_OPEN_LAUNCHER;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 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;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
@@ -38,26 +41,28 @@
 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.ShelfPeekAnim.ShelfAnimState.HIDE;
-import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.PEEK;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 
 import android.animation.Animator;
-import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.PointF;
+import android.graphics.Rect;
 import android.os.Build;
 import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
 import android.view.WindowInsets;
 import android.view.animation.Interpolator;
+import android.widget.Toast;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -67,23 +72,29 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.BaseActivityInterface.AnimationFactory;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
+import com.android.quickstep.util.InputConsumerProxy;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.ShelfPeekAnim;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
+import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -95,17 +106,34 @@
 import com.android.systemui.shared.system.TaskInfoCompat;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
 /**
  * Handles the navigation gestures when Launcher is the default home activity.
- * TODO: Merge this with BaseSwipeUpHandler
  */
-@TargetApi(Build.VERSION_CODES.O)
-public abstract class BaseSwipeUpHandlerV2<T extends StatefulActivity<?>, Q extends RecentsView>
-        extends BaseSwipeUpHandler<T, Q> implements OnApplyWindowInsetsListener {
-    private static final String TAG = BaseSwipeUpHandlerV2.class.getSimpleName();
+@TargetApi(Build.VERSION_CODES.R)
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+        extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
+        RecentsAnimationCallbacks.RecentsAnimationListener {
+    private static final String TAG = "AbsSwipeUpHandler";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
 
+    protected final BaseActivityInterface<?, T> mActivityInterface;
+    protected final InputConsumerProxy mInputConsumerProxy;
+    protected final ActivityInitListener mActivityInitListener;
+    // Callbacks to be made once the recents animation starts
+    private final ArrayList<Runnable> mRecentsAnimationStartCallbacks = new ArrayList<>();
+    protected RecentsAnimationController mRecentsAnimationController;
+    protected RecentsAnimationTargets mRecentsAnimationTargets;
+    protected T mActivity;
+    protected Q mRecentsView;
+    protected Runnable mGestureEndCallback;
+    protected MultiStateCallback mStateCallback;
+    protected boolean mCanceled;
+    private boolean mRecentsViewScrollLinked = false;
+
     private static int getFlagForIndex(int index, String name) {
         if (DEBUG_STATES) {
             STATE_NAMES[index] = name;
@@ -175,15 +203,15 @@
 
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim mRunningWindowAnim;
-    private boolean mIsShelfPeeking;
+    private boolean mIsMotionPaused;
+    private boolean mHasMotionEverBeenPaused;
 
     private boolean mContinuingLastGesture;
 
     private ThumbnailData mTaskSnapshot;
 
     // Used to control launcher components throughout the swipe gesture.
-    private AnimatorPlaybackController mLauncherTransitionController;
-    private boolean mHasLauncherTransitionControllerStarted;
+    private AnimatorControllerWithResistance mLauncherTransitionController;
 
     private AnimationFactory mAnimationFactory = (t) -> { };
 
@@ -201,11 +229,15 @@
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
-    public BaseSwipeUpHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
+    public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
             InputConsumerController inputConsumer) {
-        super(context, deviceState, gestureState, inputConsumer);
+        super(context, deviceState, gestureState, new TransformParams());
+        mActivityInterface = gestureState.getActivityInterface();
+        mActivityInitListener = mActivityInterface.createActivityInitListener(this::onActivityInit);
+        mInputConsumerProxy =
+                new InputConsumerProxy(inputConsumer, this::createNewInputProxyHandler);
         mTaskAnimationManager = taskAnimationManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
@@ -274,9 +306,17 @@
         }
     }
 
-    @Override
     protected boolean onActivityInit(Boolean alreadyOnHome) {
-        super.onActivityInit(alreadyOnHome);
+        T createdActivity = mActivityInterface.getCreatedActivity();
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.1");
+        }
+        if (createdActivity != null) {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.PAUSE_NOT_DETECTED, "BaseSwipeUpHandler.2");
+            }
+            initTransitionEndpoints(createdActivity.getDeviceProfile());
+        }
         final T activity = mActivityInterface.getCreatedActivity();
         if (mActivity == activity) {
             return true;
@@ -315,7 +355,9 @@
         return true;
     }
 
-    @Override
+    /**
+     * Return true if the window should be translated horizontally if the recents view scrolls
+     */
     protected boolean moveWindowWithRecentsScroll() {
         return mGestureState.getEndTarget() != HOME;
     }
@@ -394,7 +436,7 @@
         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
                 () -> mDeviceState.getRotationTouchHelper().
                         onEndTargetCalculated(mGestureState.getEndTarget(),
-                        mActivityInterface));
+                                mActivityInterface));
 
         notifyGestureStartedAsync();
     }
@@ -444,9 +486,20 @@
                 .getHighResLoadingState().setVisible(true);
     }
 
-    @Override
-    public void onMotionPauseChanged(boolean isPaused) {
-        setShelfState(isPaused ? PEEK : HIDE, ShelfPeekAnim.INTERPOLATOR, ShelfPeekAnim.DURATION);
+    public MotionPauseDetector.OnMotionPauseListener getMotionPauseListener() {
+        return new MotionPauseDetector.OnMotionPauseListener() {
+            @Override
+            public void onMotionPauseDetected() {
+                mHasMotionEverBeenPaused = true;
+                maybeUpdateRecentsAttachedState();
+                performHapticFeedback();
+            }
+
+            @Override
+            public void onMotionPauseChanged(boolean isPaused) {
+                mIsMotionPaused = isPaused;
+            }
+        };
     }
 
     public void maybeUpdateRecentsAttachedState() {
@@ -477,12 +530,25 @@
             // The window is going away so make sure recents is always visible in this case.
             recentsAttachedToAppWindow = true;
         } else {
-            recentsAttachedToAppWindow = mIsShelfPeeking || mIsLikelyToStartNewTask;
+            recentsAttachedToAppWindow = mHasMotionEverBeenPaused || mIsLikelyToStartNewTask;
         }
         mAnimationFactory.setRecentsAttachedToAppWindow(recentsAttachedToAppWindow, animate);
+
+        // Reapply window transform throughout the attach animation, as the animation affects how
+        // much the window is bound by overscroll (vs moving freely).
+        if (animate) {
+            ValueAnimator reapplyWindowTransformAnim = ValueAnimator.ofFloat(0, 1);
+            reapplyWindowTransformAnim.addUpdateListener(anim -> {
+                if (mRunningWindowAnim == null) {
+                    applyWindowTransform();
+                }
+            });
+            reapplyWindowTransformAnim.setDuration(RECENTS_ATTACH_DURATION).start();
+        } else {
+            applyWindowTransform();
+        }
     }
 
-    @Override
     public void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask) {
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, true /* animate */);
     }
@@ -494,19 +560,6 @@
         }
     }
 
-    @UiThread
-    public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
-        mAnimationFactory.setShelfState(shelfState, interpolator, duration);
-        boolean wasShelfPeeking = mIsShelfPeeking;
-        mIsShelfPeeking = shelfState == PEEK;
-        if (mIsShelfPeeking != wasShelfPeeking) {
-            maybeUpdateRecentsAttachedState();
-        }
-        if (shelfState.shouldPreformHaptic) {
-            performHapticFeedback();
-        }
-    }
-
     private void buildAnimationController() {
         if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
@@ -517,11 +570,11 @@
 
     /**
      * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
-     * (it has its own animation) or if we're already animating the current controller.
+     * (it has its own animation).
      * @return Whether we can create the launcher controller or update its progress.
      */
     private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
-        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+        return mGestureState.getEndTarget() != HOME;
     }
 
     @Override
@@ -531,18 +584,20 @@
         return result;
     }
 
-    private void onAnimatorPlaybackControllerCreated(AnimatorPlaybackController anim) {
+    private void onAnimatorPlaybackControllerCreated(AnimatorControllerWithResistance anim) {
         mLauncherTransitionController = anim;
-        mLauncherTransitionController.dispatchSetInterpolator(t -> t * mDragLengthFactor);
-        mLauncherTransitionController.dispatchOnStart();
+        mLauncherTransitionController.getNormalController().dispatchOnStart();
         updateLauncherTransitionProgress();
     }
 
-    @Override
     public Intent getLaunchIntent() {
         return mGestureState.getOverviewIntent();
     }
 
+    /**
+     * Called when the value of {@link #mCurrentShift} changes
+     */
+    @UiThread
     @Override
     public void updateFinalShift() {
         final boolean passed = mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW;
@@ -571,10 +626,7 @@
                 || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
-        // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
-        // anyway. The controller mimics the drag length factor by applying it to its interpolators.
-        float progress = mCurrentShift.value / mDragLengthFactor;
-        mLauncherTransitionController.setPlayFraction(progress);
+        mLauncherTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
     }
 
     /**
@@ -603,7 +655,41 @@
     public void onRecentsAnimationStart(RecentsAnimationController controller,
             RecentsAnimationTargets targets) {
         ActiveGestureLog.INSTANCE.addLog("startRecentsAnimationCallback", targets.apps.length);
-        super.onRecentsAnimationStart(controller, targets);
+        mRecentsAnimationController = controller;
+        mRecentsAnimationTargets = targets;
+        mTransformParams.setTargetSet(mRecentsAnimationTargets);
+        RemoteAnimationTargetCompat runningTaskTarget = targets.findTask(
+                mGestureState.getRunningTaskId());
+
+        if (runningTaskTarget != null) {
+            mTaskViewSimulator.setPreview(runningTaskTarget);
+        }
+
+        // Only initialize the device profile, if it has not been initialized before, as in some
+        // configurations targets.homeContentInsets may not be correct.
+        if (mActivity == null) {
+            DeviceProfile dp = mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile();
+            if (targets.minimizedHomeBounds != null && runningTaskTarget != null) {
+                Rect overviewStackBounds = mActivityInterface
+                        .getOverviewWindowBounds(targets.minimizedHomeBounds, runningTaskTarget);
+                dp = dp.getMultiWindowProfile(mContext,
+                        new WindowBounds(overviewStackBounds, targets.homeContentInsets));
+            } else {
+                // If we are not in multi-window mode, home insets should be same as system insets.
+                dp = dp.copy(mContext);
+            }
+            dp.updateInsets(targets.homeContentInsets);
+            dp.updateIsSeascape(mContext);
+            initTransitionEndpoints(dp);
+        }
+
+        // Notify when the animation starts
+        if (!mRecentsAnimationStartCallbacks.isEmpty()) {
+            for (Runnable action : new ArrayList<>(mRecentsAnimationStartCallbacks)) {
+                action.run();
+            }
+            mRecentsAnimationStartCallbacks.clear();
+        }
 
         // Only add the callback to enable the input consumer after we actually have the controller
         mStateCallback.runOnceAtState(STATE_APP_CONTROLLER_RECEIVED | STATE_GESTURE_STARTED,
@@ -620,10 +706,14 @@
         mStateCallback.setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED);
 
         // Defer clearing the controller and the targets until after we've updated the state
-        super.onRecentsAnimationCanceled(thumbnailData);
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
     }
 
-    @Override
+    @UiThread
     public void onGestureStarted(boolean isLikelyToStartNewTask) {
         notifyGestureStartedAsync();
         setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
@@ -647,7 +737,7 @@
     /**
      * Called as a result on ACTION_CANCEL to return the UI to the start state.
      */
-    @Override
+    @UiThread
     public void onGestureCancelled() {
         updateDisplacement(0);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -660,11 +750,12 @@
      * @param velocity The x and y components of the velocity when the gesture ends.
      * @param downPos The x and y value of where the gesture started.
      */
-    @Override
+    @UiThread
     public void onGestureEnded(float endVelocity, PointF velocity, PointF downPos) {
         float flingThreshold = mContext.getResources()
-                .getDimension(R.dimen.quickstep_fling_threshold_velocity);
-        boolean isFling = mGestureStarted && Math.abs(endVelocity) > flingThreshold;
+                .getDimension(R.dimen.quickstep_fling_threshold_speed);
+        boolean isFling = mGestureStarted && !mIsMotionPaused
+                && Math.abs(endVelocity) > flingThreshold;
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
 
         mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
@@ -678,7 +769,10 @@
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
     }
 
-    @Override
+    /**
+     * Called to create a input proxy for the running task
+     */
+    @UiThread
     protected InputConsumer createNewInputProxyHandler() {
         endRunningWindowAnim(mGestureState.getEndTarget() == HOME /* cancel */);
         endLauncherTransitionController();
@@ -719,7 +813,7 @@
         ActiveGestureLog.INSTANCE.addLog("onSettledOnEndTarget " + mGestureState.getEndTarget());
     }
 
-    @Override
+    /** @return Whether this was the task we were waiting to appear, and thus handled it. */
     protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return false;
@@ -754,7 +848,7 @@
             if (isCancel) {
                 endTarget = LAST_TASK;
             } else if (mDeviceState.isFullyGesturalNavMode()) {
-                if (mIsShelfPeeking) {
+                if (mIsMotionPaused) {
                     endTarget = RECENTS;
                 } else if (goingToNewTask) {
                     endTarget = NEW_TASK;
@@ -776,7 +870,7 @@
 
             if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !willGoToNewTaskOnSwipeUp) {
                 endTarget = HOME;
-            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp && !mIsShelfPeeking) {
+            } else if (mDeviceState.isFullyGesturalNavMode() && isSwipeUp) {
                 // If swiping at a diagonal, base end target on the faster velocity.
                 endTarget = NEW_TASK;
             } else if (isSwipeUp) {
@@ -796,7 +890,6 @@
     @UiThread
     private void handleNormalGestureEnd(float endVelocity, boolean isFling, PointF velocity,
             boolean isCancel) {
-        PointF velocityPxPerMs = new PointF(velocity.x / 1000, velocity.y / 1000);
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget = calculateEndTarget(velocity, endVelocity,
@@ -811,14 +904,12 @@
             startShift = currentShift;
             interpolator = endTarget == RECENTS ? OVERSHOOT_1_2 : DEACCEL;
         } else {
-            startShift = Utilities.boundToRange(currentShift - velocityPxPerMs.y
+            startShift = Utilities.boundToRange(currentShift - velocity.y
                     * getSingleFrameMs(mContext) / mTransitionDragLength, 0, mDragLengthFactor);
-            float minFlingVelocity = mContext.getResources()
-                    .getDimension(R.dimen.quickstep_fling_min_velocity);
-            if (Math.abs(endVelocity) > minFlingVelocity && mTransitionDragLength > 0) {
+            if (mTransitionDragLength > 0) {
                 if (endTarget == RECENTS && !mDeviceState.isFullyGesturalNavMode()) {
                     Interpolators.OvershootParams overshoot = new Interpolators.OvershootParams(
-                            startShift, endShift, endShift, endVelocity / 1000,
+                            startShift, endShift, endShift, endVelocity,
                             mTransitionDragLength, mContext);
                     endShift = overshoot.end;
                     interpolator = overshoot.interpolator;
@@ -830,7 +921,7 @@
                     // we want the page's snap velocity to approximately match the velocity at
                     // which the user flings, so we scale the duration by a value near to the
                     // derivative of the scroll interpolator at zero, ie. 2.
-                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocityPxPerMs.y));
+                    long baseDuration = Math.round(Math.abs(distanceToTravel / velocity.y));
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
 
                     if (endTarget == RECENTS) {
@@ -840,13 +931,10 @@
             }
         }
 
-        if (endTarget.isLauncher && mRecentsAnimationController != null) {
-            mRecentsAnimationController.enableInputProxy(mInputConsumer,
-                    this::createNewInputProxyHandler);
+        if (endTarget.isLauncher) {
+            mInputConsumerProxy.enable();
         }
-
         if (endTarget == HOME) {
-            setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
             duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
         } else if (endTarget == RECENTS) {
             LiveTileOverlay.INSTANCE.startIconAnimation();
@@ -862,9 +950,6 @@
                 }
                 duration = Math.max(duration, mRecentsView.getScroller().getDuration());
             }
-            if (mDeviceState.isFullyGesturalNavMode()) {
-                setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
-            }
         }
 
         // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
@@ -876,7 +961,7 @@
             mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
         }
 
-        animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
+        animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocity);
     }
 
     private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
@@ -963,6 +1048,7 @@
         }
 
         if (mGestureState.getEndTarget() == HOME) {
+            mTaskViewSimulator.setDrawsBelowRecents(false);
             HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
             RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
@@ -1023,31 +1109,6 @@
             windowAnim.start();
             mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
         }
-        // Always play the entire launcher animation when going home, since it is separate from
-        // the animation that has been controlled thus far.
-        if (mGestureState.getEndTarget() == HOME) {
-            start = 0;
-        }
-
-        // We want to use the same interpolator as the window, but need to adjust it to
-        // interpolate over the remaining progress (end - start).
-        TimeInterpolator adjustedInterpolator = Interpolators.mapToProgress(
-                interpolator, start, end);
-        if (mLauncherTransitionController == null) {
-            return;
-        }
-        if (start == end || duration <= 0) {
-            mLauncherTransitionController.dispatchSetInterpolator(t -> end);
-        } else {
-            mLauncherTransitionController.dispatchSetInterpolator(adjustedInterpolator);
-        }
-        mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
-
-        if (UNSTABLE_SPRINGS.get()) {
-            mLauncherTransitionController.dispatchOnStart();
-        }
-        mLauncherTransitionController.getAnimationPlayer().start();
-        mHasLauncherTransitionControllerStarted = true;
     }
 
     private void computeRecentsScrollIfInvisible() {
@@ -1083,7 +1144,7 @@
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
+                Trace.beginAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
                 if (mActivity != null) {
                     removeLiveTileOverlay();
                 }
@@ -1098,11 +1159,19 @@
                 maybeUpdateRecentsAttachedState(false);
                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
             }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+            }
         });
+        if (mRecentsAnimationTargets != null) {
+            mRecentsAnimationTargets.addReleaseCheck(anim);
+        }
         return anim;
     }
 
-    @Override
     public void onConsumerAboutToBeSwitched() {
         if (mActivity != null) {
             // In the off chance that the gesture ends before Launcher is started, we should clear
@@ -1131,15 +1200,6 @@
 
     @UiThread
     private void startNewTask() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mRecentsAnimationController.finish(true /* toRecents */, this::startNewTaskInternal);
-        } else {
-            startNewTaskInternal();
-        }
-    }
-
-    @UiThread
-    private void startNewTaskInternal() {
         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
         startNewTask(success -> {
             if (!success) {
@@ -1153,9 +1213,19 @@
         });
     }
 
-    @Override
+    /**
+     * Called when we successfully startNewTask() on the task that was previously running. Normally
+     * we call resumeLastTask() when returning to the previously running task, but this handles a
+     * specific edge case: if we switch from A to B, and back to A before B appears, we need to
+     * start A again to ensure it stays on top.
+     */
+    @androidx.annotation.CallSuper
     protected void onRestartPreviouslyAppearedTask() {
-        super.onRestartPreviouslyAppearedTask();
+        // Finish the controller here, since we won't get onTaskAppeared() for a task that already
+        // appeared.
+        if (mRecentsAnimationController != null) {
+            mRecentsAnimationController.finish(false, null);
+        }
         reset();
     }
 
@@ -1170,13 +1240,10 @@
     private void cancelCurrentAnimation() {
         mCanceled = true;
         mCurrentShift.cancelAnimation();
-        if (mLauncherTransitionController != null && mLauncherTransitionController
-                .getAnimationPlayer().isStarted()) {
-            mLauncherTransitionController.getAnimationPlayer().cancel();
-        }
     }
 
     private void invalidateHandler() {
+        mInputConsumerProxy.destroy();
         endRunningWindowAnim(false /* cancel */);
 
         if (mGestureEndCallback != null) {
@@ -1196,9 +1263,11 @@
     }
 
     private void endLauncherTransitionController() {
-        setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
         if (mLauncherTransitionController != null) {
-            mLauncherTransitionController.getAnimationPlayer().end();
+            // End the animation, but stay at the same visual progress.
+            mLauncherTransitionController.getNormalController().dispatchSetInterpolator(
+                    t -> Utilities.boundToRange(mCurrentShift.value, 0, 1));
+            mLauncherTransitionController.getNormalController().getAnimationPlayer().end();
             mLauncherTransitionController = null;
         }
     }
@@ -1259,7 +1328,7 @@
                     // new thumbnail
                     finishTransitionPosted = ViewUtils.postDraw(taskView,
                             () -> mStateCallback.setStateOnUiThread(STATE_SCREENSHOT_CAPTURED),
-                                    this::isCanceled);
+                            this::isCanceled);
                 }
             }
             if (!finishTransitionPosted) {
@@ -1328,4 +1397,171 @@
         return app.isNotInRecents
                 || app.activityType == ACTIVITY_TYPE_HOME;
     }
+
+    /**
+     * To be called at the end of constructor of subclasses. This calls various methods which can
+     * depend on proper class initialization.
+     */
+    protected void initAfterSubclassConstructor() {
+        initTransitionEndpoints(
+                mTaskViewSimulator.getOrientationState().getLauncherDeviceProfile());
+    }
+
+    protected void performHapticFeedback() {
+        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+    }
+
+    public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
+        return mRecentsView != null ? mRecentsView.getEventDispatcher(navbarRotation) : null;
+    }
+
+    public void setGestureEndCallback(Runnable gestureEndCallback) {
+        mGestureEndCallback = gestureEndCallback;
+    }
+
+    protected void linkRecentsViewScroll() {
+        SurfaceTransactionApplier.create(mRecentsView, applier -> {
+            mTransformParams.setSyncTransactionApplier(applier);
+            runOnRecentsAnimationStart(() ->
+                    mRecentsAnimationTargets.addReleaseCheck(applier));
+        });
+
+        mRecentsView.setOnScrollChangeListener((v, scrollX, scrollY, oldScrollX, oldScrollY) -> {
+            if (moveWindowWithRecentsScroll()) {
+                updateFinalShift();
+            }
+        });
+        runOnRecentsAnimationStart(() ->
+                mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController,
+                        mRecentsAnimationTargets));
+        mRecentsViewScrollLinked = true;
+    }
+
+    protected void startNewTask(Consumer<Boolean> resultCallback) {
+        // Launch the task user scrolled to (mRecentsView.getNextPage()).
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            // We finish recents animation inside launchTask() when live tile is enabled.
+            mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
+                    true /* freezeTaskList */);
+        } else {
+            if (!mCanceled) {
+                TaskView nextTask = mRecentsView.getNextPageTaskView();
+                if (nextTask != null) {
+                    int taskId = nextTask.getTask().key.id;
+                    mGestureState.updateLastStartedTaskId(taskId);
+                    boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
+                            .contains(taskId);
+                    nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
+                            success -> {
+                                resultCallback.accept(success);
+                                if (success) {
+                                    if (hasTaskPreviouslyAppeared) {
+                                        onRestartPreviouslyAppearedTask();
+                                    }
+                                } else {
+                                    mActivityInterface.onLaunchTaskFailed();
+                                    nextTask.notifyTaskLaunchFailed(TAG);
+                                    mRecentsAnimationController.finish(true /* toRecents */, null);
+                                }
+                            }, MAIN_EXECUTOR.getHandler());
+                } else {
+                    mActivityInterface.onLaunchTaskFailed();
+                    Toast.makeText(mContext, R.string.activity_not_available, LENGTH_SHORT).show();
+                    mRecentsAnimationController.finish(true /* toRecents */, null);
+                }
+            }
+            mCanceled = false;
+        }
+    }
+
+    /**
+     * Runs the given {@param action} if the recents animation has already started, or queues it to
+     * be run when it is next started.
+     */
+    protected void runOnRecentsAnimationStart(Runnable action) {
+        if (mRecentsAnimationTargets == null) {
+            mRecentsAnimationStartCallbacks.add(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /**
+     * TODO can we remove this now that we don't finish the controller until onTaskAppeared()?
+     * @return whether the recents animation has started and there are valid app targets.
+     */
+    protected boolean hasTargets() {
+        return mRecentsAnimationTargets != null && mRecentsAnimationTargets.hasTargets();
+    }
+
+    @Override
+    public void onRecentsAnimationFinished(RecentsAnimationController controller) {
+        mRecentsAnimationController = null;
+        mRecentsAnimationTargets = null;
+        if (mRecentsView != null) {
+            mRecentsView.setRecentsAnimationTargets(null, null);
+        }
+    }
+
+    @Override
+    public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+        if (mRecentsAnimationController != null) {
+            if (handleTaskAppeared(appearedTaskTarget)) {
+                mRecentsAnimationController.finish(false /* toRecents */,
+                        null /* onFinishComplete */);
+                mActivityInterface.onLaunchTaskSuccess();
+                ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimation", false);
+            }
+        }
+    }
+
+    /**
+     * @return The index of the TaskView in RecentsView whose taskId matches the task that will
+     * resume if we finish the controller.
+     */
+    protected int getLastAppearedTaskIndex() {
+        return mGestureState.getLastAppearedTaskId() != -1
+                ? mRecentsView.getTaskIndexForId(mGestureState.getLastAppearedTaskId())
+                : mRecentsView.getRunningTaskIndex();
+    }
+
+    /**
+     * @return Whether we are continuing a gesture that already landed on a new task,
+     * but before that task appeared.
+     */
+    protected boolean hasStartedNewTask() {
+        return mGestureState.getLastStartedTaskId() != -1;
+    }
+
+    /**
+     * Registers a callback to run when the activity is ready.
+     * @param intent The intent that will be used to start the activity if it doesn't exist already.
+     */
+    public void initWhenReady(Intent intent) {
+        // Preload the plan
+        RecentsModel.INSTANCE.get(mContext).getTasks(null);
+
+        mActivityInitListener.register(intent);
+    }
+
+    /**
+     * Applies the transform on the recents animation
+     */
+    protected void applyWindowTransform() {
+        if (mWindowTransitionController != null) {
+            mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
+        }
+        if (mRecentsAnimationTargets != null) {
+            if (mRecentsViewScrollLinked) {
+                mTaskViewSimulator.setScroll(mRecentsView.getScrollOffset());
+            }
+            mTaskViewSimulator.apply(mTransformParams);
+        }
+    }
+
+    public interface Factory {
+
+        AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
+                GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
+    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
rename to quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 9310685..55f5424 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -79,8 +79,8 @@
         BaseActivityInterface.AnimationFactory factory = mActivityInterface.prepareRecentsUI(
                 mDeviceState,
                 wasVisible, (controller) -> {
-                    controller.dispatchOnStart();
-                    controller.getAnimationPlayer().end();
+                    controller.getNormalController().dispatchOnStart();
+                    controller.getNormalController().getAnimationPlayer().end();
                 });
         factory.createActivityInterface(RECENTS_LAUNCH_DURATION);
         factory.setRecentsAttachedToAppWindow(true, false);
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index f4a394a..8b108ac 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -15,12 +15,10 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.RECENTS_ATTACH_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.RECENTS_ATTACH_DURATION;
 import static com.android.quickstep.SysUINavigationMode.getMode;
 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -28,6 +26,8 @@
 import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import android.animation.Animator;
 import android.annotation.TargetApi;
@@ -36,7 +36,6 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.view.MotionEvent;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -52,7 +51,7 @@
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
-import com.android.quickstep.util.ShelfPeekAnim;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -106,7 +105,7 @@
     public abstract void onAssistantVisibilityChanged(float visibility);
 
     public abstract AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback);
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback);
 
     public abstract ActivityInitListener createActivityInitListener(
             Predicate<Boolean> onInitListener);
@@ -297,9 +296,6 @@
 
         default void onTransitionCancelled() { }
 
-        default void setShelfState(ShelfPeekAnim.ShelfAnimState animState,
-                Interpolator interpolator, long duration) { }
-
         /**
          * @param attached Whether to show RecentsView alongside the app window. If false, recents
          *                 will be hidden by some property we can animate, e.g. alpha.
@@ -312,11 +308,11 @@
 
         protected final ACTIVITY_TYPE mActivity;
         private final STATE_TYPE mStartState;
-        private final Consumer<AnimatorPlaybackController> mCallback;
+        private final Consumer<AnimatorControllerWithResistance> mCallback;
 
         private boolean mIsAttachedToWindow;
 
-        DefaultAnimationFactory(Consumer<AnimatorPlaybackController> callback) {
+        DefaultAnimationFactory(Consumer<AnimatorControllerWithResistance> callback) {
             mCallback = callback;
 
             mActivity = getCreatedActivity();
@@ -344,7 +340,14 @@
             controller.setEndAction(() -> mActivity.getStateManager().goToState(
                     controller.getInterpolatedProgress() > 0.5 ? mOverviewState : mBackgroundState,
                     false));
-            mCallback.accept(controller);
+
+            RecentsView recentsView = mActivity.getOverviewPanel();
+            AnimatorControllerWithResistance controllerWithResistance =
+                    AnimatorControllerWithResistance.createForRecents(controller, mActivity,
+                            recentsView.getPagedViewOrientedState(), mActivity.getDeviceProfile(),
+                            recentsView, RECENTS_SCALE_PROPERTY, recentsView,
+                            TASK_SECONDARY_TRANSLATION);
+            mCallback.accept(controllerWithResistance);
 
             // Creating the activity controller animation sometimes reapplies the launcher state
             // (because we set the animation as the current state animation), so we reapply the
@@ -393,13 +396,13 @@
         protected void createBackgroundToOverviewAnim(ACTIVITY_TYPE activity, PendingAnimation pa) {
             //  Scale down recents from being full screen to being in overview.
             RecentsView recentsView = activity.getOverviewPanel();
-            pa.addFloat(recentsView, SCALE_PROPERTY,
+            pa.addFloat(recentsView, RECENTS_SCALE_PROPERTY,
                     recentsView.getMaxScaleForFullScreen(), 1, LINEAR);
             pa.addFloat(recentsView, FULLSCREEN_PROGRESS, 1, 0, LINEAR);
         }
     }
 
     protected static boolean showOverviewActions(Context context) {
-        return ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context);
+        return removeShelfFromOverview(context);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
rename to quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index d1da0c1..3898f0b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -27,11 +27,11 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
@@ -84,7 +84,7 @@
     /** 6 */
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
         factory.initUI();
         return factory;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
similarity index 99%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
rename to quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index f60a50b..ffb05df 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -71,7 +71,7 @@
  */
 @TargetApi(Build.VERSION_CODES.R)
 public class FallbackSwipeHandler extends
-        BaseSwipeUpHandlerV2<RecentsActivity, FallbackRecentsView> {
+        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
 
     /**
      * Message used for receiving gesture nav contract information. We use a static messenger to
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java b/quickstep/src/com/android/quickstep/ImageActionsApi.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/ImageActionsApi.java
rename to quickstep/src/com/android/quickstep/ImageActionsApi.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
rename to quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index b020355..036d473 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -27,7 +27,6 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -39,9 +38,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
 import com.android.launcher3.statemanager.StateManager;
@@ -50,8 +47,8 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.shared.LauncherOverlayManager;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -119,16 +116,10 @@
 
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
-            boolean activityVisible, Consumer<AnimatorPlaybackController> callback) {
+            boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
         notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
-            public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator,
-                    long duration) {
-                mActivity.getShelfPeekAnim().setShelfState(shelfState, interpolator, duration);
-            }
-
-            @Override
             protected void createBackgroundToOverviewAnim(BaseQuickstepLauncher activity,
                     PendingAnimation pa) {
                 super.createBackgroundToOverviewAnim(activity, pa);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
rename to quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 052d0a6..4411455 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -39,7 +39,7 @@
  * Temporary class to allow easier refactoring
  */
 public class LauncherSwipeHandlerV2 extends
-        BaseSwipeUpHandlerV2<BaseQuickstepLauncher, RecentsView> {
+        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 0710a0a..eb33f98 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 
 import java.io.PrintWriter;
 
@@ -85,7 +85,7 @@
      * QUICKSTEP_ROTATION_UNINITIALIZED, then user has not tapped on an active nav region.
      * Otherwise it will be the rotation of the display when the user first interacted with the
      * active nav bar region.
-     * The "session" ends when {@link #enableMultipleRegions(boolean, DefaultDisplay.Info)} is
+     * The "session" ends when {@link #enableMultipleRegions(boolean, Info)} is
      * called - usually from a timeout or if user starts interacting w/ the foreground app.
      *
      * This is different than {@link #mLastRectTouched} as it can get reset by the system whereas
@@ -108,7 +108,7 @@
         mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
     }
 
-    private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+    private void refreshTouchRegion(Info info, Resources newRes) {
         // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
         // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
         // It tries to cache and reuse swipe regions whenever possible based only on rotation
@@ -117,7 +117,7 @@
         resetSwipeRegions(info);
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+    void setNavigationMode(SysUINavigationMode.Mode newMode, Info info,
             Resources newRes) {
         if (mMode == newMode) {
             return;
@@ -126,7 +126,7 @@
         refreshTouchRegion(info, newRes);
     }
 
-    void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+    void setGesturalHeight(int newGesturalHeight, Info info, Resources newRes) {
         if (mNavBarGesturalHeight == newGesturalHeight) {
             return;
         }
@@ -140,9 +140,9 @@
      * alongside other regions.
      * Ok to call multiple times
      *
-     * @see #enableMultipleRegions(boolean, DefaultDisplay.Info)
+     * @see #enableMultipleRegions(boolean, Info)
      */
-    void createOrAddTouchRegion(DefaultDisplay.Info info) {
+    void createOrAddTouchRegion(Info info) {
         mCurrentDisplayRotation = info.rotation;
         if (mQuickStepStartingRotation > QUICKSTEP_ROTATION_UNINITIALIZED
                 && mCurrentDisplayRotation == mQuickStepStartingRotation) {
@@ -170,7 +170,7 @@
      * @param enableMultipleRegions Set to true to start tracking multiple nav bar regions
      * @param info The current displayInfo which will be the start of the quickswitch gesture
      */
-    void enableMultipleRegions(boolean enableMultipleRegions, DefaultDisplay.Info info) {
+    void enableMultipleRegions(boolean enableMultipleRegions, Info info) {
         mEnableMultipleRegions = enableMultipleRegions &&
                 mMode != SysUINavigationMode.Mode.TWO_BUTTONS;
         if (mEnableMultipleRegions) {
@@ -191,7 +191,7 @@
      *
      * @param displayInfo The display whos rotation will be used as the current active rotation
      */
-    void setSingleActiveRegion(DefaultDisplay.Info displayInfo) {
+    void setSingleActiveRegion(Info displayInfo) {
         mActiveTouchRotation = displayInfo.rotation;
         resetSwipeRegions(displayInfo);
     }
@@ -202,7 +202,7 @@
      * To be called whenever we want to stop tracking more than one swipe region.
      * Ok to call multiple times.
      */
-    private void resetSwipeRegions(DefaultDisplay.Info region) {
+    private void resetSwipeRegions(Info region) {
         if (DEBUG) {
             Log.d(TAG, "clearing all regions except rotation: " + mCurrentDisplayRotation);
         }
@@ -226,7 +226,7 @@
         }
     }
 
-    private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
+    private OrientationRectF createRegionForDisplay(Info display) {
         if (DEBUG) {
             Log.d(TAG, "creating rotation region for: " + mCurrentDisplayRotation);
         }
@@ -374,7 +374,7 @@
         StringBuilder regions = new StringBuilder("  currentTouchableRotations=");
         for(int i = 0; i < mSwipeTouchRegions.size(); i++) {
             OrientationRectF rectF = mSwipeTouchRegions.get(mSwipeTouchRegions.keyAt(i));
-            regions.append(rectF.mRotation).append(" ");
+            regions.append(rectF).append(" ");
         }
         pw.println(regions.toString());
         pw.println("  mNavBarGesturalHeight=" + mNavBarGesturalHeight);
diff --git a/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java b/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
new file mode 100644
index 0000000..4c261ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OverscrollPluginFactory.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
+
+import com.android.launcher3.R;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.ResourceBasedOverride;
+import com.android.systemui.plugins.OverscrollPlugin;
+
+/**
+ * Resource overrideable factory for forcing a local overscroll plugin.
+ * Override {@link R.string#overscroll_plugin_factory_class} to set a different class.
+ */
+public class OverscrollPluginFactory implements ResourceBasedOverride {
+    public static final MainThreadInitializedObject<OverscrollPluginFactory> INSTANCE = forOverride(
+            OverscrollPluginFactory.class,
+            R.string.overscroll_plugin_factory_class);
+
+    /**
+     * Get the plugin that is defined locally in launcher, as opposed to a dynamic side loaded one.
+     */
+    public OverscrollPlugin getLocalOverscrollPlugin() {
+        return null;
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
rename to quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index dca3378..365cff5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -29,7 +29,6 @@
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
@@ -87,12 +86,6 @@
         MAIN_EXECUTOR.execute(new HideRecentsCommand());
     }
 
-    @BinderThread
-    public void onTip(int actionType, int viewType) {
-        MAIN_EXECUTOR.execute(() ->
-                UserEventDispatcher.newInstance(mContext).logActionTip(actionType, viewType));
-    }
-
     private class ShowRecentsCommand extends RecentsActivityCommand {
 
         private final boolean mTriggeredFromAltTab;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
similarity index 88%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
rename to quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 6f4d34c..39af0db 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -53,9 +53,15 @@
                         Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
             }
 
-            case TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED: {
+            case TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get());
+                        FeatureFlags.ENABLE_OVERVIEW_SHARE.get());
+                return response;
+            }
+
+            case TestProtocol.REQUEST_OVERVIEW_CONTENT_PUSH_ENABLED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        FeatureFlags.ENABLE_OVERVIEW_CONTENT_PUSH.get());
                 return response;
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
rename to quickstep/src/com/android/quickstep/RecentsActivity.java
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 4e9aa61..51f5e5d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -15,49 +15,30 @@
  */
 package com.android.quickstep;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputEvent;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.InputConsumerController;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.Consumer;
-import java.util.function.Supplier;
 
 /**
  * Wrapper around RecentsAnimationControllerCompat to help with some synchronization
  */
 public class RecentsAnimationController {
 
-    private static final String TAG = "RecentsAnimationController";
-
     private final RecentsAnimationControllerCompat mController;
     private final Consumer<RecentsAnimationController> mOnFinishedListener;
     private final boolean mAllowMinimizeSplitScreen;
 
-    private InputConsumerController mInputConsumerController;
-    private Supplier<InputConsumer> mInputProxySupplier;
-    private InputConsumer mInputConsumer;
     private boolean mUseLauncherSysBarFlags = false;
     private boolean mSplitScreenMinimized = false;
-    private boolean mTouchInProgress;
-    private boolean mDisableInputProxyPending;
 
     public RecentsAnimationController(RecentsAnimationControllerCompat controller,
             boolean allowMinimizeSplitScreen,
@@ -136,12 +117,12 @@
 
     @UiThread
     public void finishAnimationToHome() {
-        finishAndDisableInputProxy(true /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     @UiThread
     public void finishAnimationToApp() {
-        finishAndDisableInputProxy(false /* toRecents */, null, false /* sendUserLeaveHint */);
+        finishController(false /* toRecents */, null, false /* sendUserLeaveHint */);
     }
 
     /** See {@link #finish(boolean, Runnable, boolean)} */
@@ -160,18 +141,6 @@
     @UiThread
     public void finish(boolean toRecents, Runnable onFinishComplete, boolean sendUserLeaveHint) {
         Preconditions.assertUIThread();
-        if (toRecents && mTouchInProgress) {
-            // Finish the controller as requested, but don't disable input proxy yet.
-            mDisableInputProxyPending = true;
-            finishController(toRecents, onFinishComplete, sendUserLeaveHint);
-        } else {
-            finishAndDisableInputProxy(toRecents, onFinishComplete, sendUserLeaveHint);
-        }
-    }
-
-    private void finishAndDisableInputProxy(boolean toRecents, Runnable onFinishComplete,
-            boolean sendUserLeaveHint) {
-        disableInputProxy();
         finishController(toRecents, onFinishComplete, sendUserLeaveHint);
     }
 
@@ -179,7 +148,6 @@
     public void finishController(boolean toRecents, Runnable callback, boolean sendUserLeaveHint) {
         mOnFinishedListener.accept(this);
         UI_HELPER_EXECUTOR.execute(() -> {
-            mController.setInputConsumerEnabled(false);
             mController.finish(toRecents, sendUserLeaveHint);
             if (callback != null) {
                 MAIN_EXECUTOR.execute(callback);
@@ -197,75 +165,8 @@
         });
     }
 
-    public void enableInputProxy(InputConsumerController inputConsumerController,
-            Supplier<InputConsumer> inputProxySupplier) {
-        mInputProxySupplier = inputProxySupplier;
-        mInputConsumerController = inputConsumerController;
-        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
-    }
-
     /** @return wrapper controller. */
     public RecentsAnimationControllerCompat getController() {
         return mController;
     }
-
-    private void disableInputProxy() {
-        if (mInputConsumer != null && mTouchInProgress) {
-            long now = SystemClock.uptimeMillis();
-            MotionEvent dummyCancel = MotionEvent.obtain(now,  now, ACTION_CANCEL, 0, 0, 0);
-            mInputConsumer.onMotionEvent(dummyCancel);
-            dummyCancel.recycle();
-        }
-        if (mInputConsumerController != null) {
-            mInputConsumerController.setInputListener(null);
-        }
-        mInputProxySupplier = null;
-    }
-
-    private boolean onInputConsumerEvent(InputEvent ev) {
-        if (ev instanceof MotionEvent) {
-            onInputConsumerMotionEvent((MotionEvent) ev);
-        } else if (ev instanceof KeyEvent) {
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-            mInputConsumer.onKeyEvent((KeyEvent) ev);
-            return true;
-        }
-        return false;
-    }
-
-    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
-        int action = ev.getAction();
-
-        // Just to be safe, verify that ACTION_DOWN comes before any other action,
-        // and ignore any ACTION_DOWN after the first one (though that should not happen).
-        if (!mTouchInProgress && action != ACTION_DOWN) {
-            Log.w(TAG, "Received non-down motion before down motion: " + action);
-            return false;
-        }
-        if (mTouchInProgress && action == ACTION_DOWN) {
-            Log.w(TAG, "Received down motion while touch was already in progress");
-            return false;
-        }
-
-        if (action == ACTION_DOWN) {
-            mTouchInProgress = true;
-            if (mInputConsumer == null) {
-                mInputConsumer = mInputProxySupplier.get();
-            }
-        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
-            // Finish any pending actions
-            mTouchInProgress = false;
-            if (mDisableInputProxyPending) {
-                mDisableInputProxyPending = false;
-                disableInputProxy();
-            }
-        }
-        if (mInputConsumer != null) {
-            mInputConsumer.onMotionEvent(ev);
-        }
-
-        return true;
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 2868b1b..d8064a2 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,8 +17,8 @@
 
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -56,7 +56,10 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
@@ -76,12 +79,14 @@
  */
 public class RecentsAnimationDeviceState implements
         NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener,
+        DisplayInfoChangeListener,
         OneHandedModeChangeListener {
 
+    static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
+
     private final Context mContext;
     private final SysUINavigationMode mSysUiNavMode;
-    private final DefaultDisplay mDefaultDisplay;
+    private final DisplayHolder mDisplayHolder;
     private final int mDisplayId;
     private final RotationTouchHelper mRotationTouchHelper;
 
@@ -96,6 +101,7 @@
     private float mAssistantVisibility;
     private boolean mIsOneHandedModeEnabled;
     private boolean mIsSwipeToNotificationEnabled;
+    private final boolean mIsOneHandedModeSupported;
 
     private boolean mIsUserUnlocked;
     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -117,12 +123,17 @@
     private boolean mIsUserSetupComplete;
 
     public RecentsAnimationDeviceState(Context context) {
+        this(context, DisplayController.getDefaultDisplay(context));
+    }
+
+    public RecentsAnimationDeviceState(Context context, DisplayHolder displayHolder) {
         mContext = context;
+        mDisplayHolder = displayHolder;
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
-        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
-        mDisplayId = mDefaultDisplay.getInfo().id;
-        runOnDestroy(() -> mDefaultDisplay.removeChangeListener(this));
-        mRotationTouchHelper = new RotationTouchHelper(context);
+        mDisplayId = mDisplayHolder.getInfo().id;
+        mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
+        runOnDestroy(() -> mDisplayHolder.removeChangeListener(this));
+        mRotationTouchHelper = new RotationTouchHelper(context, mDisplayHolder);
         runOnDestroy(mRotationTouchHelper::destroy);
 
         // Register for user unlocked if necessary
@@ -165,13 +176,15 @@
             }
         }
 
-        if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
+        if (mIsOneHandedModeSupported) {
             SecureSettingsObserver oneHandedEnabledObserver =
                     SecureSettingsObserver.newOneHandedSettingsObserver(
                             mContext, enabled -> mIsOneHandedModeEnabled = enabled);
             oneHandedEnabledObserver.register();
             oneHandedEnabledObserver.dispatchOnChange();
             runOnDestroy(oneHandedEnabledObserver::unregister);
+        } else {
+            mIsOneHandedModeEnabled = false;
         }
 
         SecureSettingsObserver swipeBottomEnabledObserver =
@@ -226,9 +239,9 @@
 
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
-        mDefaultDisplay.removeChangeListener(this);
-        mDefaultDisplay.addChangeListener(this);
-        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
 
         if (newMode == NO_BUTTON) {
             mExclusionListener.register();
@@ -236,12 +249,12 @@
             mExclusionListener.unregister();
         }
 
-        mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
+        mNavBarPosition = new NavBarPosition(newMode, mDisplayHolder.getInfo());
         mMode = newMode;
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != getDisplayId() || flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
@@ -443,7 +456,7 @@
     }
 
     /**
-     * @return whether screen pinning is enabled and active
+     * @return whether one-handed mode is enabled and active
      */
     public boolean isOneHandedModeActive() {
         return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
@@ -518,15 +531,18 @@
      * @return whether the given motion event can trigger the one handed mode.
      */
     public boolean canTriggerOneHandedAction(MotionEvent ev) {
-        if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
+        if (!mIsOneHandedModeSupported) {
             return false;
         }
 
-        final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
-        return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
-            && displayInfo.rotation != Surface.ROTATION_90
-            && displayInfo.rotation != Surface.ROTATION_270
-            && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
+        if (mIsOneHandedModeEnabled || mIsSwipeToNotificationEnabled) {
+            final Info displayInfo = mDisplayHolder.getInfo();
+            return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
+                && displayInfo.rotation != Surface.ROTATION_90
+                && displayInfo.rotation != Surface.ROTATION_270
+                && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
+        }
+        return false;
     }
 
     public boolean isOneHandedModeEnabled() {
@@ -553,6 +569,7 @@
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
         pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
         pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
+        pw.println("  deferredGestureRegion=" + mDeferredGestureRegion);
         mRotationTouchHelper.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 6f54ba2..6c302ae 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -160,9 +160,9 @@
 
     @Override
     public void onTaskRemoved(int taskId) {
-        Task.TaskKey dummyKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
-        mThumbnailCache.remove(dummyKey);
-        mIconCache.onTaskRemoved(dummyKey);
+        Task.TaskKey stubKey = new Task.TaskKey(taskId, 0, null, null, 0, 0);
+        mThumbnailCache.remove(stubKey);
+        mIconCache.onTaskRemoved(stubKey);
     }
 
     public void onTrimMemory(int level) {
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 2b5e42a..2cf3212 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -17,8 +17,8 @@
 
 import static android.view.Surface.ROTATION_0;
 
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ALL;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_FRAME_DELAY;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ALL;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_FRAME_DELAY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.THREE_BUTTONS;
 
@@ -28,8 +28,9 @@
 import android.view.OrientationEventListener;
 
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.DisplayController.DisplayHolder;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -40,10 +41,10 @@
 
 public class RotationTouchHelper implements
         SysUINavigationMode.NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener {
+        DisplayInfoChangeListener {
 
     private final OrientationTouchTransformer mOrientationTouchTransformer;
-    private final DefaultDisplay mDefaultDisplay;
+    private final DisplayHolder mDisplayHolder;
     private final SysUINavigationMode mSysUiNavMode;
     private final int mDisplayId;
     private int mDisplayRotation;
@@ -120,12 +121,12 @@
 
     private final Context mContext;
 
-    public RotationTouchHelper(Context context) {
+    public RotationTouchHelper(Context context, DisplayHolder displayHolder) {
         mContext = context;
+        mDisplayHolder = displayHolder;
         Resources resources = mContext.getResources();
         mSysUiNavMode = SysUINavigationMode.INSTANCE.get(context);
-        mDefaultDisplay = DefaultDisplay.INSTANCE.get(context);
-        mDisplayId = mDefaultDisplay.getInfo().id;
+        mDisplayId = mDisplayHolder.getInfo().id;
 
         mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
                 () -> QuickStepContract.getWindowCornerRadius(resources));
@@ -200,7 +201,7 @@
             return;
         }
 
-        mOrientationTouchTransformer.createOrAddTouchRegion(mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayHolder.getInfo());
     }
 
     /**
@@ -222,11 +223,11 @@
 
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
-        mDefaultDisplay.removeChangeListener(this);
-        mDefaultDisplay.addChangeListener(this);
-        onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
+        mDisplayHolder.removeChangeListener(this);
+        mDisplayHolder.addChangeListener(this);
+        onDisplayInfoChanged(mDisplayHolder.getInfo(), CHANGE_ALL);
 
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayHolder.getInfo(),
             mContext.getResources());
         if (!mMode.hasGestures && newMode.hasGestures) {
             setupOrientationSwipeHandler();
@@ -242,7 +243,7 @@
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != mDisplayId|| flags == CHANGE_FRAME_DELAY) {
             // ignore displays that aren't running launcher and frame refresh rate changes
             return;
@@ -275,7 +276,7 @@
      * Sets the gestural height.
      */
     void setGesturalHeight(int newGesturalHeight) {
-        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDisplayHolder.getInfo(),
             mContext.getResources());
     }
 
@@ -292,7 +293,7 @@
     }
 
     private void enableMultipleRegions(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.enableMultipleRegions(enable, mDisplayHolder.getInfo());
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
         if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
             // Clear any previous state from sensor manager
@@ -355,7 +356,7 @@
      * notifies system UI of the primary rotation the user is interacting with
      */
     private void toggleSecondaryNavBarsForRotation() {
-        mOrientationTouchTransformer.setSingleActiveRegion(mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.setSingleActiveRegion(mDisplayHolder.getInfo());
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
similarity index 86%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
rename to quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 07faab7..b6eaa1c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -16,7 +16,7 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
 
 import android.animation.Animator;
 import android.content.Context;
@@ -24,7 +24,6 @@
 import android.graphics.Matrix.ScaleToFit;
 import android.graphics.Rect;
 import android.graphics.RectF;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
@@ -35,6 +34,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -45,7 +45,6 @@
 public abstract class SwipeUpAnimationLogic {
 
     protected static final Rect TEMP_RECT = new Rect();
-    private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
 
     protected DeviceProfile mDp;
 
@@ -66,12 +65,8 @@
     protected int mTransitionDragLength;
     // How much further we can drag past recents, as a factor of mTransitionDragLength.
     protected float mDragLengthFactor = 1;
-    // Start resisting when swiping past this factor of mTransitionDragLength.
-    private float mDragLengthFactorStartPullback = 1f;
-    // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private float mDragLengthFactorMaxPullback = 1f;
 
-    protected AnimatorPlaybackController mWindowTransitionController;
+    protected AnimatorControllerWithResistance mWindowTransitionController;
 
     public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
             GestureState gestureState, TransformParams transformParams) {
@@ -84,6 +79,7 @@
         mTaskViewSimulator.setLayoutRotation(
                 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
                 mDeviceState.getRotationTouchHelper().getDisplayRotation());
+        mTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     protected void initTransitionEndpoints(DeviceProfile dp) {
@@ -97,19 +93,17 @@
         if (mDeviceState.isFullyGesturalNavMode()) {
             // We can drag all the way to the top of the screen.
             mDragLengthFactor = (float) dp.heightPx / mTransitionDragLength;
-
-            float startScale = mTaskViewSimulator.getFullScreenScale();
-            // Start pulling back when RecentsView scale is 0.75f, and let it go down to 0.5f.
-            mDragLengthFactorStartPullback = (0.75f - startScale) / (1 - startScale);
-            mDragLengthFactorMaxPullback = (0.5f - startScale) / (1 - startScale);
         } else {
-            mDragLengthFactor = 1;
-            mDragLengthFactorStartPullback = mDragLengthFactorMaxPullback = 1;
+            mDragLengthFactor = 1 + AnimatorControllerWithResistance.TWO_BUTTON_EXTRA_DRAG_FACTOR;
         }
 
         PendingAnimation pa = new PendingAnimation(mTransitionDragLength * 2);
-        mTaskViewSimulator.addAppToOverviewAnim(pa, t -> t * mDragLengthFactor);
-        mWindowTransitionController = pa.createPlaybackController();
+        mTaskViewSimulator.addAppToOverviewAnim(pa, LINEAR);
+        AnimatorPlaybackController normalController = pa.createPlaybackController();
+        mWindowTransitionController = AnimatorControllerWithResistance.createForRecents(
+                normalController, mContext, mTaskViewSimulator.getOrientationState(),
+                mDp, mTaskViewSimulator.recentsViewScale, AnimatedFloat.VALUE,
+                mTaskViewSimulator.recentsViewSecondaryTranslation, AnimatedFloat.VALUE);
     }
 
     @UiThread
@@ -122,13 +116,6 @@
         } else {
             float translation = Math.max(displacement, 0);
             shift = mTransitionDragLength == 0 ? 0 : translation / mTransitionDragLength;
-            if (shift > mDragLengthFactorStartPullback) {
-                float pullbackProgress = Utilities.getProgress(shift,
-                        mDragLengthFactorStartPullback, mDragLengthFactor);
-                pullbackProgress = PULLBACK_INTERPOLATOR.getInterpolation(pullbackProgress);
-                shift = mDragLengthFactorStartPullback + pullbackProgress
-                        * (mDragLengthFactorMaxPullback - mDragLengthFactorStartPullback);
-            }
         }
 
         mCurrentShift.updateValue(shift);
@@ -183,7 +170,7 @@
             HomeAnimationFactory homeAnimationFactory) {
         final RectF targetRect = homeAnimationFactory.getWindowTargetRect();
 
-        mWindowTransitionController.setPlayFraction(startProgress / mDragLengthFactor);
+        mCurrentShift.updateValue(startProgress);
         mTaskViewSimulator.apply(mTransformParams.setProgress(startProgress));
         RectF cropRectF = new RectF(mTaskViewSimulator.getCurrentCropRect());
 
@@ -287,7 +274,6 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            super.onAnimationStart(animation);
             mHomeAnim.dispatchOnStart();
         }
 
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 6994d66..6b50218 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -17,6 +17,9 @@
 package com.android.quickstep;
 
 import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_2_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_3_BUTTON;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.content.BroadcastReceiver;
@@ -25,6 +28,7 @@
 import android.util.Log;
 
 import com.android.launcher3.ResourceUtils;
+import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
@@ -38,16 +42,18 @@
 public class SysUINavigationMode {
 
     public enum Mode {
-        THREE_BUTTONS(false, 0),
-        TWO_BUTTONS(true, 1),
-        NO_BUTTON(true, 2);
+        THREE_BUTTONS(false, 0, LAUNCHER_NAVIGATION_MODE_3_BUTTON),
+        TWO_BUTTONS(true, 1, LAUNCHER_NAVIGATION_MODE_2_BUTTON),
+        NO_BUTTON(true, 2, LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON);
 
         public final boolean hasGestures;
         public final int resValue;
+        public final LauncherEvent launcherEvent;
 
-        Mode(boolean hasGestures, int resValue) {
+        Mode(boolean hasGestures, int resValue, LauncherEvent launcherEvent) {
             this.hasGestures = hasGestures;
             this.resValue = resValue;
+            this.launcherEvent = launcherEvent;
         }
     }
 
@@ -183,12 +189,10 @@
     }
 
     public interface NavigationModeChangeListener {
-
         void onNavigationModeChanged(Mode newMode);
     }
 
     public interface OneHandedModeChangeListener {
-
         void onOneHandedModeChanged(int newGesturalHeight);
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
similarity index 87%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskOverlayFactory.java
rename to quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index db512fa..3a47024 100644
--- a/quickstep/recents_ui_overrides/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.util.MainThreadInitializedObject.forOverride;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL;
 import static com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED;
 
@@ -39,13 +39,12 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.OverviewActionsView;
+import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskThumbnailView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.plugins.OverscrollPlugin;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -91,20 +90,22 @@
         return shortcuts;
     }
 
-    public static final MainThreadInitializedObject<TaskOverlayFactory> INSTANCE =
-            forOverride(TaskOverlayFactory.class, R.string.task_overlay_factory_class);
-
-    /**
-     * @return a launcher-provided OverscrollPlugin if available, otherwise null
-     */
-    public OverscrollPlugin getLocalOverscrollPlugin() {
-        return null;
-    }
-
     public TaskOverlay createOverlay(TaskThumbnailView thumbnailView) {
         return new TaskOverlay(thumbnailView);
     }
 
+    /**
+     * Subclasses can attach any system listeners in this method, must be paired with
+     * {@link #removeListeners()}
+     */
+    public void initListeners() { }
+
+    /**
+     * Subclasses should remove any system listeners in this method, must be paired with
+     * {@link #initListeners()}
+     */
+    public void removeListeners() { }
+
     /** Note that these will be shown in order from top to bottom, if available for the task. */
     private static final TaskShortcutFactory[] MENU_OPTIONS = new TaskShortcutFactory[]{
             TaskShortcutFactory.APP_INFO,
@@ -156,6 +157,7 @@
                 getActionsView().setCallbacks(new OverlayUICallbacks() {
                     @Override
                     public void onShare() {
+                        endLiveTileMode(isAllowedByPolicy);
                         if (isAllowedByPolicy) {
                             mImageApi.startShareActivity();
                         } else {
@@ -166,6 +168,7 @@
                     @SuppressLint("NewApi")
                     @Override
                     public void onScreenshot() {
+                        endLiveTileMode(isAllowedByPolicy);
                         saveScreenshot(task);
                     }
                 });
@@ -173,6 +176,23 @@
         }
 
         /**
+         * End rendering live tile in Overview.
+         *
+         * @param showScreenshot if it's true, we take a screenshot and switch to it.
+         */
+        public void endLiveTileMode(boolean showScreenshot) {
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+                RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+                if (showScreenshot) {
+                    recentsView.switchToScreenshot(
+                            () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
+                } else {
+                    recentsView.finishRecentsAnimation(true /* toRecents */, null);
+                }
+            }
+        }
+
+        /**
          * Called to save screenshot of the task thumbnail.
          */
         @SuppressLint("NewApi")
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
rename to quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ff051b6..3b245b3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -18,7 +18,6 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
@@ -310,16 +309,11 @@
     TaskShortcutFactory WELLBEING = (activity, view) ->
             WellbeingModel.SHORTCUT_FACTORY.getShortcut(activity, view.getItemInfo());
 
-    TaskShortcutFactory SCREENSHOT = (activity, tv) -> {
-        if (ENABLE_OVERVIEW_ACTIONS.get()) {
-            return tv.getThumbnail().getTaskOverlay()
-                .getScreenshotShortcut(activity, tv.getItemInfo());
-        }
-        return null;
-    };
+    TaskShortcutFactory SCREENSHOT = (activity, tv) -> tv.getThumbnail().getTaskOverlay()
+            .getScreenshotShortcut(activity, tv.getItemInfo());
 
     TaskShortcutFactory MODAL = (activity, tv) -> {
-        if (ENABLE_OVERVIEW_ACTIONS.get() && ENABLE_OVERVIEW_SELECTIONS.get()) {
+        if (ENABLE_OVERVIEW_SELECTIONS.get()) {
             return tv.getThumbnail().getTaskOverlay().getModalStateSystemShortcut(tv.getItemInfo());
         }
         return null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
rename to quickstep/src/com/android/quickstep/TaskViewUtils.java
index e2e25f3..a5af181 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
@@ -146,7 +146,7 @@
         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
         // RecentsView never updates the display rotation until swipe-up so the value may be stale.
         // Use the display value instead.
-        int displayRotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
 
         TaskViewSimulator topMostSimulator = null;
         if (targets.apps.length > 0) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
similarity index 91%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
rename to quickstep/src/com/android/quickstep/TouchInteractionService.java
index e5852be..ebdc867 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -22,7 +22,6 @@
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_INPUT_MONITOR;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
@@ -61,13 +60,12 @@
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.tracing.nano.LauncherTraceProto;
-import com.android.launcher3.tracing.nano.TouchInteractionServiceProto;
+import com.android.launcher3.tracing.LauncherTraceProto;
+import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.TraceHelper;
@@ -101,31 +99,13 @@
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Wrapper around a list for processing arguments.
- */
-class ArgList extends LinkedList<String> {
-    public ArgList(List<String> l) {
-        super(l);
-    }
-
-    public String peekArg() {
-        return peekFirst();
-    }
-
-    public String nextArg() {
-        return pollFirst().toLowerCase();
-    }
-}
 
 /**
  * Service connected by system-UI for handling touch interaction.
  */
 @TargetApi(Build.VERSION_CODES.R)
 public class TouchInteractionService extends Service implements PluginListener<OverscrollPlugin>,
-        ProtoTraceable<LauncherTraceProto> {
+        ProtoTraceable<LauncherTraceProto.Builder> {
 
     private static final String TAG = "TouchInteractionService";
 
@@ -159,7 +139,6 @@
         }
 
         @BinderThread
-        @Override
         public void onOverviewToggle() {
             TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
             mOverviewCommandHelper.onOverviewToggle();
@@ -183,7 +162,7 @@
         @BinderThread
         @Override
         public void onTip(int actionType, int viewType) {
-            mOverviewCommandHelper.onTip(actionType, viewType);
+            // Please delete this method from the interface
         }
 
         @BinderThread
@@ -207,18 +186,7 @@
         @BinderThread
         public void onBackAction(boolean completed, int downX, int downY, boolean isButton,
                 boolean gestureSwipeLeft) {
-            if (mOverviewComponentObserver == null) {
-                return;
-            }
-
-            final BaseActivityInterface activityInterface =
-                    mOverviewComponentObserver.getActivityInterface();
-            UserEventDispatcher.newInstance(getBaseContext()).logActionBack(completed, downX, downY,
-                    isButton, gestureSwipeLeft, activityInterface.getContainerType());
-
-            if (completed && !isButton && shouldNotifyBackGesture()) {
-                UI_HELPER_EXECUTOR.execute(TouchInteractionService.this::tryNotifyBackGesture);
-            }
+            // Remove this method from the interface
         }
 
         @BinderThread
@@ -238,23 +206,6 @@
             WindowBounds wb = new WindowBounds(bounds, insets);
             MAIN_EXECUTOR.execute(() -> SplitScreenBounds.INSTANCE.setSecondaryWindowBounds(wb));
         }
-
-        /** Deprecated methods **/
-        public void onQuickStep(MotionEvent motionEvent) { }
-
-        public void onQuickScrubEnd() { }
-
-        public void onQuickScrubProgress(float progress) { }
-
-        public void onQuickScrubStart() { }
-
-        public void onPreMotionEvent(int downHitTarget) { }
-
-        public void onMotionEvent(MotionEvent ev) {
-            ev.recycle();
-        }
-
-        public void onBind(ISystemUiProxy iSystemUiProxy) { }
     };
 
     private static boolean sConnected = false;
@@ -269,9 +220,9 @@
         return sIsInitialized;
     }
 
-    private final BaseSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
+    private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
             this::createLauncherSwipeHandler;
-    private final BaseSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
+    private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
             this::createFallbackSwipeHandler;
 
     private ActivityManagerWrapper mAM;
@@ -298,10 +249,10 @@
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this);
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
-        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         ProtoTracer.INSTANCE.get(this).add(this);
 
         sConnected = true;
@@ -417,9 +368,12 @@
 
             // Update the tracing state
             if ((mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_TRACING_ENABLED) != 0) {
-                ProtoTracer.INSTANCE.get(TouchInteractionService.this).start();
+                Log.d(TAG, "Starting tracing.");
+                ProtoTracer.INSTANCE.get(this).start();
             } else {
-                ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+                Log.d(TAG, "Stopping tracing. Dumping to file="
+                    + ProtoTracer.INSTANCE.get(this).getTraceFile());
+                ProtoTracer.INSTANCE.get(this).stop();
             }
         }
     }
@@ -443,7 +397,7 @@
         disposeEventHandlers();
         mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).setProxy(null);
-        ProtoTracer.INSTANCE.get(TouchInteractionService.this).stop();
+        ProtoTracer.INSTANCE.get(this).stop();
         ProtoTracer.INSTANCE.get(this).remove(this);
 
         getSystemService(AccessibilityManager.class)
@@ -552,6 +506,7 @@
             reset();
         }
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
+        ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
     private GestureState createGestureState(GestureState previousGestureState) {
@@ -611,9 +566,8 @@
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
                 OverscrollPlugin plugin = null;
                 if (FeatureFlags.FORCE_LOCAL_OVERSCROLL_PLUGIN.get()) {
-                    TaskOverlayFactory factory =
-                            TaskOverlayFactory.INSTANCE.get(getApplicationContext());
-                    plugin = factory.getLocalOverscrollPlugin();  // may be null
+                    plugin = OverscrollPluginFactory.INSTANCE.get(
+                            getApplicationContext()).getLocalOverscrollPlugin();
                 }
 
                 // If not local plugin was forced, use the actual overscroll plugin if available.
@@ -695,17 +649,17 @@
                     runningComponent != null && runningComponent.equals(homeComponent);
         }
 
-        if (gestureState.getRunningTask() == null) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()
+                && gestureState.getActivityInterface().isInLiveTileMode()) {
+            return createOverviewInputConsumer(
+                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
+        } else if (gestureState.getRunningTask() == null) {
             return mResetGestureInputConsumer;
         } else if (previousGestureState.isRunningAnimationToLauncher()
                 || gestureState.getActivityInterface().isResumed()
                 || forceOverviewInputConsumer) {
             return createOverviewInputConsumer(
                     previousGestureState, gestureState, event, forceOverviewInputConsumer);
-        } else if (ENABLE_QUICKSTEP_LIVE_TILE.get()
-                && gestureState.getActivityInterface().isInLiveTileMode()) {
-            return createOverviewInputConsumer(
-                    previousGestureState, gestureState, event, forceOverviewInputConsumer);
         } else if (mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())) {
             return mResetGestureInputConsumer;
         } else {
@@ -716,7 +670,7 @@
     private InputConsumer createOtherActivityInputConsumer(GestureState gestureState,
             MotionEvent event) {
 
-        final BaseSwipeUpHandler.Factory factory;
+        final AbsSwipeUpHandler.Factory factory;
         if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
             factory = mFallbackSwipeHandlerFactory;
         } else {
@@ -837,10 +791,10 @@
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] rawArgs) {
         if (rawArgs.length > 0 && Utilities.IS_DEBUG_DEVICE) {
-            ArgList args = new ArgList(Arrays.asList(rawArgs));
-            switch (args.nextArg()) {
+            LinkedList<String> args = new LinkedList(Arrays.asList(rawArgs));
+            switch (args.pollFirst()) {
                 case "cmd":
-                    if (args.peekArg() == null) {
+                    if (args.peekFirst() == null) {
                         printAvailableCommands(pw);
                     } else {
                         onCommand(pw, args);
@@ -871,8 +825,7 @@
             pw.println("  mConsumer=" + mConsumer.getName());
             ActiveGestureLog.INSTANCE.dump("", pw);
             pw.println("ProtoTrace:");
-            pw.println("  file="
-                    + ProtoTracer.INSTANCE.get(TouchInteractionService.this).getTraceFile());
+            pw.println("  file=" + ProtoTracer.INSTANCE.get(this).getTraceFile());
         }
     }
 
@@ -881,21 +834,21 @@
         pw.println("  clear-touch-log: Clears the touch interaction log");
     }
 
-    private void onCommand(PrintWriter pw, ArgList args) {
-        switch (args.nextArg()) {
+    private void onCommand(PrintWriter pw, LinkedList<String> args) {
+        switch (args.pollFirst()) {
             case "clear-touch-log":
                 ActiveGestureLog.INSTANCE.clear();
                 break;
         }
     }
 
-    private BaseSwipeUpHandler createLauncherSwipeHandler(
+    private AbsSwipeUpHandler createLauncherSwipeHandler(
             GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
         return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
     }
 
-    private BaseSwipeUpHandler createFallbackSwipeHandler(
+    private AbsSwipeUpHandler createFallbackSwipeHandler(
             GestureState gestureState, long touchTimeMs, boolean continuingLastGesture) {
         return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
                 gestureState, touchTimeMs, continuingLastGesture, mInputConsumer);
@@ -928,11 +881,10 @@
     }
 
     @Override
-    public void writeToProto(LauncherTraceProto proto) {
-        if (proto.touchInteractionService == null) {
-            proto.touchInteractionService = new TouchInteractionServiceProto();
-        }
-        proto.touchInteractionService.serviceConnected = true;
-        proto.touchInteractionService.serviceConnected = true;
+    public void writeToProto(LauncherTraceProto.Builder proto) {
+        TouchInteractionServiceProto.Builder serviceProto =
+            TouchInteractionServiceProto.newBuilder();
+        serviceProto.setServiceConnected(true);
+        proto.setTouchInteractionService(serviceProto);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/ViewUtils.java
rename to quickstep/src/com/android/quickstep/ViewUtils.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
index be3fdde..a4a7bd3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -21,7 +21,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.SysUINavigationMode;
@@ -43,7 +43,7 @@
         SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
         if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
             NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
-                    DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+                    DisplayController.getDefaultDisplay(mActivity).getInfo());
             mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
                     true /* disableHorizontalSwipe */, navBarPosition,
                     null /* onInterceptTouch */, this);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
similarity index 89%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 3f1e7ba..24a7610 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -15,17 +15,19 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
+import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
@@ -82,10 +84,12 @@
                 MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
 
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndOffset[0],
+        setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
                 config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
         setter.setFloat(mRecentsView, ADJACENT_PAGE_OFFSET, scaleAndOffset[1],
                 config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
+        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
 
         setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
similarity index 94%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
rename to quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index a946304..f5f5259 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -101,20 +101,20 @@
     }
 
     @Override
-    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+    protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
         if (mHomeTaskInfo != null && runningTaskInfo != null &&
                 mHomeTaskInfo.taskId == runningTaskInfo.taskId
                 && getTaskViewCount() == 0) {
-            // Do not add a dummy task if we are running over home with empty recents, so that we
-            // show the empty recents message instead of showing a dummy task and later removing it.
+            // Do not add a stub task if we are running over home with empty recents, so that we
+            // show the empty recents message instead of showing a stub task and later removing it.
             return false;
         }
-        return super.shouldAddDummyTaskView(runningTaskInfo);
+        return super.shouldAddStubTaskView(runningTaskInfo);
     }
 
     @Override
     protected void applyLoadPlan(ArrayList<Task> tasks) {
-        // When quick-switching on 3p-launcher, we add a "dummy" tile corresponding to Launcher
+        // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
         // 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()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsDragLayer.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
similarity index 99%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsState.java
index 211a30c..f15a9de 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -90,7 +90,6 @@
         return new float[] { NO_SCALE, NO_OFFSET };
     }
 
-
     private static class ModalState extends RecentsState {
 
         public ModalState(int id, int flags) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsTaskController.java
rename to quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index a676390..5aaea00 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -21,7 +21,8 @@
 
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MIN_PROGRESS_FOR_OVERVIEW;
+import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
+import static com.android.quickstep.AbsSwipeUpHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 
@@ -35,13 +36,12 @@
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -60,7 +60,7 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder;
 
 /**
- * A dummy input consumer used when the device is still locked, e.g. from secure camera.
+ * A placeholder input consumer used when the device is still locked, e.g. from secure camera.
  */
 public class DeviceLockedInputConsumer implements InputConsumer,
         RecentsAnimationCallbacks.RecentsAnimationListener, BuilderProxy {
@@ -115,7 +115,7 @@
                 R.dimen.device_locked_y_offset);
 
         // Do not use DeviceProfile as the user data might be locked
-        mDisplaySize = DefaultDisplay.INSTANCE.get(context).getInfo().realSize;
+        mDisplaySize = DisplayController.getDefaultDisplay(context).getInfo().realSize;
 
         // Init states
         mStateCallback = new MultiStateCallback(STATE_NAMES);
@@ -180,12 +180,11 @@
      */
     private void finishTouchTracking(MotionEvent ev) {
         if (mThresholdCrossed && ev.getAction() == ACTION_UP) {
-            mVelocityTracker.computeCurrentVelocity(1000,
-                    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity());
+            mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
 
             float velocityY = mVelocityTracker.getYVelocity();
             float flingThreshold = mContext.getResources()
-                    .getDimension(R.dimen.quickstep_fling_threshold_velocity);
+                    .getDimension(R.dimen.quickstep_fling_threshold_speed);
 
             boolean dismissTask;
             if (Math.abs(velocityY) > flingThreshold) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
similarity index 94%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index 0bb8fff..22bd334 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -52,7 +52,6 @@
 
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
-    private final PointF mStartDragPos = new PointF();
 
     private boolean mPassedSlop;
 
@@ -92,7 +91,6 @@
                 if (!mPassedSlop) {
                     if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
                             > mSquaredSlop) {
-                        mStartDragPos.set(mLastPos.x, mLastPos.y);
                         if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
                                 mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
                                 || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
@@ -104,17 +102,16 @@
                         }
                     }
                 } else {
-                    float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
-                            mLastPos.y - mStartDragPos.y);
+                    float distance = (float) Math.hypot(mLastPos.x - mDownPos.x,
+                            mLastPos.y - mDownPos.y);
                     if (distance > mDragDistThreshold && mPassedSlop) {
                         onStopGestureDetected();
                     }
                 }
                 break;
             }
-            case ACTION_UP:
-            case ACTION_CANCEL: {
-                if (mLastPos.y >= mStartDragPos.y && mPassedSlop) {
+            case ACTION_UP: {
+                if (mLastPos.y >= mDownPos.y && mPassedSlop) {
                     onStartGestureDetected();
                 }
 
@@ -122,6 +119,10 @@
                 mState = STATE_INACTIVE;
                 break;
             }
+            case ACTION_CANCEL:
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
         }
 
         if (mState != STATE_ACTIVE) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
similarity index 97%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 6259f1f..b1a1133 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -28,6 +28,7 @@
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
+import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -52,9 +53,9 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.AbsSwipeUpHandler;
+import com.android.quickstep.AbsSwipeUpHandler.Factory;
 import com.android.quickstep.BaseActivityInterface;
-import com.android.quickstep.BaseSwipeUpHandler;
-import com.android.quickstep.BaseSwipeUpHandler.Factory;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
@@ -93,7 +94,7 @@
     private final InputMonitorCompat mInputMonitorCompat;
     private final BaseActivityInterface mActivityInterface;
 
-    private final BaseSwipeUpHandler.Factory mHandlerFactory;
+    private final AbsSwipeUpHandler.Factory mHandlerFactory;
 
     private final Consumer<OtherActivityInputConsumer> mOnCompleteCallback;
     private final MotionPauseDetector mMotionPauseDetector;
@@ -101,7 +102,7 @@
 
     private VelocityTracker mVelocityTracker;
 
-    private BaseSwipeUpHandler mInteractionHandler;
+    private AbsSwipeUpHandler mInteractionHandler;
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -365,7 +366,7 @@
         mInteractionHandler = mHandlerFactory.newHandler(mGestureState, touchTimeMs,
                 mTaskAnimationManager.isRecentsAnimationRunning());
         mInteractionHandler.setGestureEndCallback(this::onInteractionGestureFinished);
-        mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler::onMotionPauseChanged);
+        mMotionPauseDetector.setOnMotionPauseListener(mInteractionHandler.getMotionPauseListener());
         Intent intent = new Intent(mInteractionHandler.getLaunchIntent());
         mInteractionHandler.initWhenReady(intent);
 
@@ -393,8 +394,7 @@
             if (ev.getActionMasked() == ACTION_CANCEL) {
                 mInteractionHandler.onGestureCancelled();
             } else {
-                mVelocityTracker.computeCurrentVelocity(1000,
-                        ViewConfiguration.get(this).getScaledMaximumFlingVelocity());
+                mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
                 float velocityX = mVelocityTracker.getXVelocity(mActivePointerId);
                 float velocityY = mVelocityTracker.getYVelocity(mActivePointerId);
                 float velocity = mNavBarPosition.isRightEdge()
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/ResetGestureInputConsumer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
similarity index 79%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index b9827ff..a8bf333 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -42,18 +42,16 @@
         mMotionPauseMinDisplacement = context.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
         mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
-        mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
-            if (isPaused) {
-                SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
-                BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
-                        .getCreatedActivity();
-                if (launcherActivity != null) {
-                    launcherActivity.getRootView().performHapticFeedback(
-                            HapticFeedbackConstants.LONG_PRESS,
-                            HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
-                }
-                mMotionPauseDetector.clear();
+        mMotionPauseDetector.setOnMotionPauseListener(() -> {
+            SystemUiProxy.INSTANCE.get(context).stopScreenPinning();
+            BaseDraggingActivity launcherActivity = gestureState.getActivityInterface()
+                    .getCreatedActivity();
+            if (launcherActivity != null) {
+                launcherActivity.getRootView().performHapticFeedback(
+                        HapticFeedbackConstants.LONG_PRESS,
+                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
             }
+            mMotionPauseDetector.clear();
         });
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
rename to quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index 0e2312b..f897ecc 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_COMPLETED;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_BAD_ANGLE;
 import static com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult.ASSISTANT_NOT_STARTED_SWIPE_TOO_SHORT;
@@ -48,6 +49,7 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -74,6 +76,7 @@
     private final PointF mAssistantStartDragPos = new PointF();
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
+    private final MotionPauseDetector mMotionPauseDetector;
     private boolean mTouchCameFromAssistantCorner;
     private boolean mTouchCameFromNavBar;
     private boolean mPassedAssistantSlop;
@@ -100,6 +103,7 @@
                 new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
                         new NavBarPosition(Mode.NO_BUTTON, displayRotation),
                         null /*onInterceptTouch*/, this);
+        mMotionPauseDetector = new MotionPauseDetector(context);
 
         final Resources resources = context.getResources();
         mBottomGestureHeight =
@@ -140,7 +144,6 @@
         if (mGestureCallback == null || mAssistantGestureActive) {
             return;
         }
-        finalVelocity.set(finalVelocity.x / 1000, finalVelocity.y / 1000);
         if (mTouchCameFromNavBar) {
             mGestureCallback.onNavBarGestureAttempted(wasFling
                     ? HOME_GESTURE_COMPLETED : OVERVIEW_GESTURE_COMPLETED, finalVelocity);
@@ -177,12 +180,14 @@
                 }
                 mLaunchedAssistant = false;
                 mSwipeUpTouchTracker.init();
+                mMotionPauseDetector.clear();
+                mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseDetected);
                 break;
             case MotionEvent.ACTION_MOVE:
+                mLastPos.set(event.getX(), event.getY());
                 if (!mAssistantGestureActive) {
                     break;
                 }
-                mLastPos.set(event.getX(), event.getY());
 
                 if (!mPassedAssistantSlop) {
                     // Normal gesture, ensure we pass the slop before we start tracking the gesture
@@ -213,6 +218,7 @@
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
+                mMotionPauseDetector.clear();
                 if (mGestureCallback != null && !intercepted && mTouchCameFromNavBar) {
                     mGestureCallback.onNavBarGestureAttempted(
                             HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION, new PointF());
@@ -239,9 +245,15 @@
         }
         mSwipeUpTouchTracker.onMotionEvent(event);
         mAssistantGestureDetector.onTouchEvent(event);
+        mMotionPauseDetector.addPosition(event);
+        mMotionPauseDetector.setDisallowPause(mLastPos.y >= mDisplaySize.y - mBottomGestureHeight);
         return intercepted;
     }
 
+    protected void onMotionPauseDetected() {
+        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+    }
+
     /**
      * Determine if angle is larger than threshold for assistant detection
      */
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 4110b33..865b66e 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -16,9 +16,9 @@
 package com.android.quickstep.interaction;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
-import static com.android.quickstep.BaseSwipeUpHandlerV2.MAX_SWIPE_DURATION;
+import static com.android.quickstep.AbsSwipeUpHandler.MAX_SWIPE_DURATION;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.HOME_NAVIGATION_COMPLETE;
 import static com.android.quickstep.interaction.TutorialController.TutorialType.OVERVIEW_NAVIGATION_COMPLETE;
 
@@ -184,8 +184,7 @@
 
         @Override
         public void updateFinalShift() {
-            float progress = mCurrentShift.value / mDragLengthFactor;
-            mWindowTransitionController.setPlayFraction(progress);
+            mWindowTransitionController.setProgress(mCurrentShift.value, mDragLengthFactor);
             mTaskViewSimulator.apply(mTransformParams);
         }
 
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index b978c09..059d158 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -59,6 +59,7 @@
 import com.android.systemui.shared.system.SysUiStatsLog;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -100,21 +101,22 @@
     }
 
     /**
-     * Logs the workspace layout information on the model thread.
+     * Logs impression of the current workspace with additional launcher events.
      */
     @Override
-    public void logSnapshot() {
+    public void logSnapshot(List<EventEnum> extraEvents) {
         LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                new SnapshotWorker());
+                new SnapshotWorker(extraEvents));
     }
 
     private class SnapshotWorker extends BaseModelUpdateTask {
-
         private final InstanceId mInstanceId;
+        private final List<EventEnum> mExtraEvents;
 
-        SnapshotWorker() {
-            mInstanceId = new InstanceIdSequence(
-                    1 << 20 /*InstanceId.INSTANCE_ID_MAX*/).newInstanceId();
+        SnapshotWorker(List<EventEnum> extraEvents) {
+            mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
+                    .newInstanceId();
+            this.mExtraEvents = extraEvents;
         }
 
         @Override
@@ -155,6 +157,9 @@
                 LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
                 writeSnapshot(atomInfo, mInstanceId);
             }
+            mExtraEvents
+                    .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
+
             getDevicePrefs(mContext).edit()
                     .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
         }
diff --git a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java b/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
deleted file mode 100644
index 9ca7f23..0000000
--- a/quickstep/src/com/android/quickstep/logging/UserEventDispatcherExtension.java
+++ /dev/null
@@ -1,86 +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.
- */
-package com.android.quickstep.logging;
-
-import android.content.Context;
-import android.util.Log;
-
-import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CANCEL_TARGET;
-import static com.android.systemui.shared.system.LauncherEventUtil.DISMISS;
-import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_QUICK_SCRUB_ONBOARDING_TIP;
-import static com.android.systemui.shared.system.LauncherEventUtil.RECENTS_SWIPE_UP_ONBOARDING_TIP;
-import static com.android.systemui.shared.system.LauncherEventUtil.VISIBLE;
-
-import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.systemui.shared.system.MetricsLoggerCompat;
-
-/**
- * This class handles AOSP MetricsLogger function calls and logging around
- * quickstep interactions.
- */
-@SuppressWarnings("unused")
-public class UserEventDispatcherExtension extends UserEventDispatcher {
-
-    public static final int ALL_APPS_PREDICTION_TIPS = 2;
-
-    private static final String TAG = "UserEventDispatcher";
-
-    public UserEventDispatcherExtension(Context context) { }
-
-    public void logStateChangeAction(int action, int dir, int downX, int downY,
-                                     int srcChildTargetType, int srcParentContainerType,
-                                     int dstContainerType, int pageIndex) {
-        new MetricsLoggerCompat().visibility(MetricsLoggerCompat.OVERVIEW_ACTIVITY,
-                dstContainerType == LauncherLogProto.ContainerType.TASKSWITCHER);
-        super.logStateChangeAction(action, dir, downX, downY, srcChildTargetType,
-                srcParentContainerType, dstContainerType, pageIndex);
-    }
-
-    public void logActionTip(int actionType, int viewType) {
-        LauncherLogProto.Action action = new LauncherLogProto.Action();
-        LauncherLogProto.Target target = new LauncherLogProto.Target();
-        switch(actionType) {
-            case VISIBLE:
-                action.type = LauncherLogProto.Action.Type.TIP;
-                target.type = LauncherLogProto.Target.Type.CONTAINER;
-                target.containerType = LauncherLogProto.ContainerType.TIP;
-                break;
-            case DISMISS:
-                action.type = LauncherLogProto.Action.Type.TOUCH;
-                action.touch = LauncherLogProto.Action.Touch.TAP;
-                target.type = LauncherLogProto.Target.Type.CONTROL;
-                target.controlType = CANCEL_TARGET;
-                break;
-            default:
-                Log.e(TAG, "Unexpected action type = " + actionType);
-        }
-
-        switch(viewType) {
-            case RECENTS_QUICK_SCRUB_ONBOARDING_TIP:
-                target.tipType = LauncherLogProto.TipType.QUICK_SCRUB_TEXT;
-                break;
-            case RECENTS_SWIPE_UP_ONBOARDING_TIP:
-                target.tipType = LauncherLogProto.TipType.SWIPE_UP_TEXT;
-                break;
-            default:
-                Log.e(TAG, "Unexpected viewType = " + viewType);
-        }
-        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
-        dispatchUserEvent(event, null);
-    }
-}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/ActiveGestureLog.java
rename to quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
new file mode 100644
index 0000000..deb70e0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static 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;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.FloatProperty;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimatorPlaybackController;
+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;
+
+/**
+ * Controls an animation that can go beyond progress = 1, at which point resistance should be
+ * applied. Internally, this is a wrapper around 2 {@link AnimatorPlaybackController}s, one that
+ * runs from progress 0 to 1 like normal, then one that seamlessly continues that animation but
+ * starts applying resistance as well.
+ */
+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);
+
+        RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
+                float translationFactor) {
+            this.scaleStartResist = scaleStartResist;
+            this.scaleMaxResist = scaleMaxResist;
+            this.translationFactor = translationFactor;
+        }
+
+        /**
+         * Start slowing down the rate of scaling down when recents view is smaller than this scale.
+         */
+        public final float scaleStartResist;
+
+        /**
+         * Recents view will reach this scale at the very end of the drag.
+         */
+        public final float scaleMaxResist;
+
+        /**
+         * How much translation to apply to RecentsView when the drag reaches the top of the screen,
+         * where 0 will keep it centered and 1 will have it barely touch the top of the screen.
+         */
+        public final float translationFactor;
+    }
+
+    private static final TimeInterpolator RECENTS_SCALE_RESIST_INTERPOLATOR = DEACCEL;
+    private static final TimeInterpolator RECENTS_TRANSLATE_RESIST_INTERPOLATOR = LINEAR;
+
+    private final AnimatorPlaybackController mNormalController;
+    private final AnimatorPlaybackController mResistanceController;
+
+    // Initialize to -1 so the first 0 gets applied.
+    private float mLastNormalProgress = -1;
+    private float mLastResistProgress;
+
+    public AnimatorControllerWithResistance(AnimatorPlaybackController normalController,
+            AnimatorPlaybackController resistanceController) {
+        mNormalController = normalController;
+        mResistanceController = resistanceController;
+    }
+
+    public AnimatorPlaybackController getNormalController() {
+        return mNormalController;
+    }
+
+    /**
+     * Applies the current progress of the animation.
+     * @param progress From 0 to maxProgress, where 1 is the target we are animating towards.
+     * @param maxProgress > 1, this is where the resistance will be applied.
+     */
+    public void setProgress(float progress, float maxProgress) {
+        float normalProgress = Utilities.boundToRange(progress, 0, 1);
+        if (normalProgress != mLastNormalProgress) {
+            mLastNormalProgress = normalProgress;
+            mNormalController.setPlayFraction(normalProgress);
+        }
+        if (maxProgress <= 1) {
+            return;
+        }
+        float resistProgress = progress <= 1 ? 0 : Utilities.getProgress(progress, 1, maxProgress);
+        if (resistProgress != mLastResistProgress) {
+            mLastResistProgress = resistProgress;
+            mResistanceController.setPlayFraction(resistProgress);
+        }
+    }
+
+    /**
+     * Applies resistance to recents when swiping up past its target position.
+     * @param normalController The controller to run from 0 to 1 before this resistance applies.
+     * @param context Used to compute start and end values.
+     * @param recentsOrientedState Used to compute start and end values.
+     * @param dp Used to compute start and end values.
+     * @param scaleTarget The target for the scaleProperty.
+     * @param scaleProperty Animate the value to change the scale of the window/recents view.
+     * @param translationTarget The target for the translationProperty.
+     * @param translationProperty Animate the value to change the translation of the recents view.
+     */
+    public static <SCALE, TRANSLATION> AnimatorControllerWithResistance createForRecents(
+            AnimatorPlaybackController normalController, Context context,
+            RecentsOrientedState recentsOrientedState, DeviceProfile dp, SCALE scaleTarget,
+            FloatProperty<SCALE> scaleProperty, TRANSLATION translationTarget,
+            FloatProperty<TRANSLATION> translationProperty) {
+
+        RecentsParams params = new RecentsParams(context, recentsOrientedState, dp, scaleTarget,
+                scaleProperty, translationTarget, translationProperty);
+        PendingAnimation resistAnim = createRecentsResistanceAnim(params);
+
+        AnimatorPlaybackController resistanceController = resistAnim.createPlaybackController();
+        return new AnimatorControllerWithResistance(normalController, resistanceController);
+    }
+
+    /**
+     * Creates the resistance animation for {@link #createForRecents}, or can be used separately
+     * when starting from recents, i.e. {@link #createRecentsResistanceFromOverviewAnim}.
+     */
+    public static <SCALE, TRANSLATION> PendingAnimation createRecentsResistanceAnim(
+            RecentsParams<SCALE, TRANSLATION> params) {
+        Rect startRect = new Rect();
+        PagedOrientationHandler orientationHandler = params.recentsOrientedState
+                .getOrientationHandler();
+        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);
+
+        PointF pivot = new PointF();
+        float fullscreenScale = params.recentsOrientedState.getFullScreenScaleAndPivot(
+                startRect, params.dp, pivot);
+        float prevScaleRate = (fullscreenScale - params.startScale)
+                / (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);
+            };
+        }
+        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);
+        }
+
+        return resistAnim;
+    }
+
+    /**
+     * Helper method to update or create a PendingAnimation suitable for animating
+     * a RecentsView interaction that started from the overview state.
+     */
+    public static PendingAnimation createRecentsResistanceFromOverviewAnim(
+            Launcher launcher, @Nullable PendingAnimation resistanceAnim) {
+        RecentsView recentsView = launcher.getOverviewPanel();
+        RecentsParams params = new RecentsParams(launcher, recentsView.getPagedViewOrientedState(),
+                launcher.getDeviceProfile(), recentsView, RECENTS_SCALE_PROPERTY, recentsView,
+                TASK_SECONDARY_TRANSLATION)
+                .setResistAnim(resistanceAnim)
+                .setResistanceParams(RecentsResistanceParams.FROM_OVERVIEW)
+                .setStartScale(recentsView.getScaleX());
+        return createRecentsResistanceAnim(params);
+    }
+
+    /**
+     * Params to compute resistance when scaling/translating recents.
+     */
+    private static class RecentsParams<SCALE, TRANSLATION> {
+        // These are all required and can't have default values, hence are final.
+        public final Context context;
+        public final RecentsOrientedState recentsOrientedState;
+        public final DeviceProfile dp;
+        public final SCALE scaleTarget;
+        public final FloatProperty<SCALE> scaleProperty;
+        public final TRANSLATION translationTarget;
+        public final FloatProperty<TRANSLATION> translationProperty;
+
+        // These are not required, or can have a default value that is generally correct.
+        @Nullable public PendingAnimation resistAnim = null;
+        public RecentsResistanceParams resistanceParams = RecentsResistanceParams.FROM_APP;
+        public float startScale = 1f;
+        public float startTranslation = 0f;
+
+        private RecentsParams(Context context, RecentsOrientedState recentsOrientedState,
+                DeviceProfile dp, SCALE scaleTarget, FloatProperty<SCALE> scaleProperty,
+                TRANSLATION translationTarget, FloatProperty<TRANSLATION> translationProperty) {
+            this.context = context;
+            this.recentsOrientedState = recentsOrientedState;
+            this.dp = dp;
+            this.scaleTarget = scaleTarget;
+            this.scaleProperty = scaleProperty;
+            this.translationTarget = translationTarget;
+            this.translationProperty = translationProperty;
+        }
+
+        private RecentsParams setResistAnim(PendingAnimation resistAnim) {
+            this.resistAnim = resistAnim;
+            return this;
+        }
+
+        private RecentsParams setResistanceParams(RecentsResistanceParams resistanceParams) {
+            this.resistanceParams = resistanceParams;
+            return this;
+        }
+
+        private RecentsParams setStartScale(float startScale) {
+            this.startScale = startScale;
+            return this;
+        }
+
+        private RecentsParams setStartTranslation(float startTranslation) {
+            this.startTranslation = startTranslation;
+            return this;
+        }
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java b/quickstep/src/com/android/quickstep/util/AssistantUtilities.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/AssistantUtilities.java
rename to quickstep/src/com/android/quickstep/util/AssistantUtilities.java
diff --git a/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
new file mode 100644
index 0000000..3e87f48
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/InputConsumerProxy.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
+
+import android.util.Log;
+import android.view.InputEvent;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+import com.android.quickstep.InputConsumer;
+import com.android.systemui.shared.system.InputConsumerController;
+
+import java.util.function.Supplier;
+
+/**
+ * Utility class which manages proxying input events from {@link InputConsumerController}
+ * to an {@link InputConsumer}
+ */
+public class InputConsumerProxy {
+
+    private static final String TAG = "InputConsumerProxy";
+
+    private final InputConsumerController mInputConsumerController;
+    private final Supplier<InputConsumer> mConsumerSupplier;
+
+    // The consumer is created lazily on demand.
+    private InputConsumer mInputConsumer;
+
+    private boolean mDestroyed = false;
+    private boolean mTouchInProgress = false;
+    private boolean mDestroyPending = false;
+
+    public InputConsumerProxy(InputConsumerController inputConsumerController,
+            Supplier<InputConsumer> consumerSupplier) {
+        mInputConsumerController = inputConsumerController;
+        mConsumerSupplier = consumerSupplier;
+    }
+
+    public void enable() {
+        if (mDestroyed) {
+            return;
+        }
+        mInputConsumerController.setInputListener(this::onInputConsumerEvent);
+    }
+
+    private boolean onInputConsumerEvent(InputEvent ev) {
+        if (ev instanceof MotionEvent) {
+            onInputConsumerMotionEvent((MotionEvent) ev);
+        } else if (ev instanceof KeyEvent) {
+            if (mInputConsumer == null) {
+                mInputConsumer = mConsumerSupplier.get();
+            }
+            mInputConsumer.onKeyEvent((KeyEvent) ev);
+            return true;
+        }
+        return false;
+    }
+
+    private boolean onInputConsumerMotionEvent(MotionEvent ev) {
+        int action = ev.getAction();
+
+        // Just to be safe, verify that ACTION_DOWN comes before any other action,
+        // and ignore any ACTION_DOWN after the first one (though that should not happen).
+        if (!mTouchInProgress && action != ACTION_DOWN) {
+            Log.w(TAG, "Received non-down motion before down motion: " + action);
+            return false;
+        }
+        if (mTouchInProgress && action == ACTION_DOWN) {
+            Log.w(TAG, "Received down motion while touch was already in progress");
+            return false;
+        }
+
+        if (action == ACTION_DOWN) {
+            mTouchInProgress = true;
+            if (mInputConsumer == null) {
+                mInputConsumer = mConsumerSupplier.get();
+            }
+        } else if (action == ACTION_CANCEL || action == ACTION_UP) {
+            // Finish any pending actions
+            mTouchInProgress = false;
+            if (mDestroyPending) {
+                destroy();
+            }
+        }
+        if (mInputConsumer != null) {
+            mInputConsumer.onMotionEvent(ev);
+        }
+
+        return true;
+    }
+
+    public void destroy() {
+        if (mTouchInProgress) {
+            mDestroyPending = true;
+            return;
+        }
+        mDestroyPending = false;
+        mDestroyed = true;
+        mInputConsumerController.setInputListener(null);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index f7bd1e2..b88a195 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
@@ -45,7 +44,7 @@
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp,
             PagedOrientationHandler orientationHandler) {
         // Track the bottom of the window.
-        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+        if (removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
             LauncherActivityInterface.INSTANCE.calculateTaskSize(context, dp, taskSize,
                     orientationHandler);
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index a5d4568..d0f6879 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -15,12 +15,13 @@
  */
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_LSQ_VELOCITY_PROVIDER;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_SYSTEM_VELOCITY_PROVIDER;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.util.Log;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 
 import com.android.launcher3.Alarm;
 import com.android.launcher3.R;
@@ -92,8 +93,8 @@
         mForcePauseTimeout = new Alarm();
         mForcePauseTimeout.setOnAlarmListener(alarm -> updatePaused(true /* isPaused */));
         mMakePauseHarderToTrigger = makePauseHarderToTrigger;
-        mVelocityProvider = ENABLE_LSQ_VELOCITY_PROVIDER.get()
-                ? new LSqVelocityProvider(axis) : new LinearVelocityProvider(axis);
+        mVelocityProvider = ENABLE_SYSTEM_VELOCITY_PROVIDER.get()
+                ? new SystemVelocityProvider(axis) : new LinearVelocityProvider(axis);
     }
 
     /**
@@ -183,11 +184,15 @@
         }
         if (mIsPaused != isPaused) {
             mIsPaused = isPaused;
+            boolean isFirstDetectedPause = !mHasEverBeenPaused && mIsPaused;
             if (mIsPaused) {
                 AccessibilityManagerCompat.sendPauseDetectedEventToTest(mContext);
                 mHasEverBeenPaused = true;
             }
             if (mOnMotionPauseListener != null) {
+                if (isFirstDetectedPause) {
+                    mOnMotionPauseListener.onMotionPauseDetected();
+                }
                 mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
             }
         }
@@ -210,7 +215,10 @@
     }
 
     public interface OnMotionPauseListener {
-        void onMotionPauseChanged(boolean isPaused);
+        /** Called only the first time motion pause is detected. */
+        void onMotionPauseDetected();
+        /** Called every time motion changes from paused to not paused and vice versa. */
+        default void onMotionPauseChanged(boolean isPaused) { }
     }
 
     /**
@@ -264,136 +272,28 @@
         }
     }
 
-    /**
-     * Java implementation of {@link android.view.VelocityTracker} using the Least Square (deg 2)
-     * algorithm.
-     */
-    private static class LSqVelocityProvider implements VelocityProvider {
+    private static class SystemVelocityProvider implements VelocityProvider {
 
-        // Maximum age of a motion event to be considered when calculating the velocity.
-        private static final long HORIZON_MS = 100;
-        // Number of samples to keep.
-        private static final int HISTORY_SIZE = 20;
-
-        // Position history are stored in a circular array
-        private final float[] mHistoricTimes = new float[HISTORY_SIZE];
-        private final float[] mHistoricPos = new float[HISTORY_SIZE];
-        private int mHistoryCount = 0;
-        private int mHistoryStart = 0;
-
+        private final VelocityTracker mVelocityTracker;
         private final int mAxis;
 
-        LSqVelocityProvider(int axis) {
+        SystemVelocityProvider(int axis) {
+            mVelocityTracker = VelocityTracker.obtain();
             mAxis = axis;
         }
 
         @Override
-        public void clear() {
-            mHistoryCount = mHistoryStart = 0;
-        }
-
-        private void addPositionAndTime(float eventTime, float eventPosition) {
-            mHistoricTimes[mHistoryStart] = eventTime;
-            mHistoricPos[mHistoryStart] = eventPosition;
-            mHistoryStart++;
-            if (mHistoryStart >= HISTORY_SIZE) {
-                mHistoryStart = 0;
-            }
-            mHistoryCount = Math.min(HISTORY_SIZE, mHistoryCount + 1);
+        public Float addMotionEvent(MotionEvent ev, int pointer) {
+            mVelocityTracker.addMovement(ev);
+            mVelocityTracker.computeCurrentVelocity(1); // px / ms
+            return mAxis == MotionEvent.AXIS_X
+                    ? mVelocityTracker.getXVelocity(pointer)
+                    : mVelocityTracker.getYVelocity(pointer);
         }
 
         @Override
-        public Float addMotionEvent(MotionEvent ev, int pointer) {
-            // Add all historic points
-            int historyCount = ev.getHistorySize();
-            for (int i = 0; i < historyCount; i++) {
-                addPositionAndTime(
-                        ev.getHistoricalEventTime(i), ev.getHistoricalAxisValue(mAxis, pointer, i));
-            }
-
-            // Start index for the last position (about to be added)
-            int eventStartIndex = mHistoryStart;
-            addPositionAndTime(ev.getEventTime(), ev.getAxisValue(mAxis, pointer));
-            return solveUnweightedLeastSquaresDeg2(eventStartIndex);
-        }
-
-        /**
-         * Solves the instantaneous velocity.
-         * Based on solveUnweightedLeastSquaresDeg2 in VelocityTracker.cpp
-         */
-        private Float solveUnweightedLeastSquaresDeg2(final int pointPos) {
-            final float eventTime = mHistoricTimes[pointPos];
-
-            float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
-            int count = 0;
-            for (int i = 0; i < mHistoryCount; i++) {
-                int index = pointPos - i;
-                if (index < 0) {
-                    index += HISTORY_SIZE;
-                }
-
-                float time = mHistoricTimes[index];
-                float age = eventTime - time;
-                if (age > HORIZON_MS) {
-                    break;
-                }
-                count++;
-                float xi = -age;
-
-                float yi = mHistoricPos[index];
-                float xi2 = xi * xi;
-                float xi3 = xi2 * xi;
-                float xi4 = xi3 * xi;
-                float xiyi = xi * yi;
-                float xi2yi = xi2 * yi;
-
-                sxi += xi;
-                sxi2 += xi2;
-                sxiyi += xiyi;
-                sxi2yi += xi2yi;
-                syi += yi;
-                sxi3 += xi3;
-                sxi4 += xi4;
-            }
-
-            if (count < 3) {
-                // Too few samples
-                if (count == 2) {
-                    int endPos = pointPos - 1;
-                    if (endPos < 0) {
-                        endPos += HISTORY_SIZE;
-                    }
-                    float denominator = eventTime - mHistoricTimes[endPos];
-                    if (denominator != 0) {
-                        return (eventTime - mHistoricPos[endPos]) / denominator;
-
-                    }
-                }
-                return null;
-            }
-
-            float Sxx = sxi2 - sxi * sxi / count;
-            float Sxy = sxiyi - sxi * syi / count;
-            float Sxx2 = sxi3 - sxi * sxi2 / count;
-            float Sx2y = sxi2yi - sxi2 * syi / count;
-            float Sx2x2 = sxi4 - sxi2 * sxi2 / count;
-
-            float denominator = Sxx * Sx2x2 - Sxx2 * Sxx2;
-            if (denominator == 0) {
-                // division by 0 when computing velocity
-                return null;
-            }
-            // Compute a
-            // float numerator = Sx2y*Sxx - Sxy*Sxx2;
-
-            // Compute b
-            float numerator = Sxy * Sx2x2 - Sx2y * Sxx2;
-            float b = numerator / denominator;
-
-            // Compute c
-            // float c = syi/count - b * sxi/count - a * sxi2/count;
-
-            return b;
+        public void clear() {
+            mVelocityTracker.clear();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/NavBarPosition.java b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
index 0a98e1b..449dba8 100644
--- a/quickstep/src/com/android/quickstep/util/NavBarPosition.java
+++ b/quickstep/src/com/android/quickstep/util/NavBarPosition.java
@@ -19,7 +19,7 @@
 
 import android.view.Surface;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.quickstep.SysUINavigationMode;
 
 /**
@@ -30,7 +30,7 @@
     private final SysUINavigationMode.Mode mMode;
     private final int mDisplayRotation;
 
-    public NavBarPosition(SysUINavigationMode.Mode mode, DefaultDisplay.Info info) {
+    public NavBarPosition(SysUINavigationMode.Mode mode, Info info) {
         mMode = mode;
         mDisplayRotation = info.rotation;
     }
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
new file mode 100644
index 0000000..4a298d3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_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;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.util.Log;
+import android.view.animation.Interpolator;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Runs an animation from overview to home. Currently, this animation is just a wrapper around the
+ * normal state transition, in order to keep RecentsView at the same scale and translationY that
+ * it started out at as it translates offscreen. It also scrolls RecentsView to page 0 and may play
+ * a {@link StaggeredWorkspaceAnim} if we're starting from an upward fling.
+ */
+public class OverviewToHomeAnim {
+
+    private static final String TAG = "OverviewToHomeAnim";
+
+    // Constants to specify how to scroll RecentsView to the default page if it's not already there.
+    private static final int DEFAULT_PAGE = 0;
+    private static final int PER_PAGE_SCROLL_DURATION = 150;
+    private static final int MAX_PAGE_SCROLL_DURATION = 750;
+
+    private final Launcher mLauncher;
+    private final Runnable mOnReachedHome;
+
+    // Only run mOnReachedHome when both of these are true.
+    private boolean mIsHomeStaggeredAnimFinished;
+    private boolean mIsOverviewHidden;
+
+    public OverviewToHomeAnim(Launcher launcher, Runnable onReachedHome) {
+        mLauncher = launcher;
+        mOnReachedHome = onReachedHome;
+    }
+
+    /**
+     * Starts the animation. If velocity < 0 (i.e. upwards), also plays a
+     * {@link StaggeredWorkspaceAnim}.
+     */
+    public void animateWithVelocity(float velocity) {
+        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        LauncherState startState = stateManager.getState();
+        if (startState != OVERVIEW) {
+            Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
+        }
+        AnimatorSet anim = new AnimatorSet();
+
+        boolean playStaggeredWorkspaceAnim = velocity < 0;
+        if (playStaggeredWorkspaceAnim) {
+            StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+                    mLauncher, velocity, false /* animateOverviewScrim */);
+            staggeredWorkspaceAnim.addAnimatorListener(new AnimationSuccessListener() {
+                @Override
+                public void onAnimationSuccess(Animator animator) {
+                    mIsHomeStaggeredAnimFinished = true;
+                    maybeOverviewToHomeAnimComplete();
+                }
+            });
+            anim.play(staggeredWorkspaceAnim.getAnimators());
+        } else {
+            mIsHomeStaggeredAnimFinished = true;
+        }
+
+        RecentsView recentsView = mLauncher.getOverviewPanel();
+        int numPagesToScroll = recentsView.getNextPage() - DEFAULT_PAGE;
+        int scrollDuration = Math.min(MAX_PAGE_SCROLL_DURATION,
+                numPagesToScroll * PER_PAGE_SCROLL_DURATION);
+        int duration = Math.max(scrollDuration, startState.getTransitionDuration(mLauncher));
+
+        StateAnimationConfig config = new UseFirstInterpolatorStateAnimConfig();
+        config.duration = duration;
+        config.animFlags = playStaggeredWorkspaceAnim
+                // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+                ? PLAY_ATOMIC_OVERVIEW_PEEK
+                : ANIM_ALL_COMPONENTS;
+        boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
+                .isLayoutNaturalToLauncher();
+        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
+                ? DEACCEL : FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
+        config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT);
+        if (!isLayoutNaturalToLauncher) {
+            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL);
+        }
+        AnimatorSet stateAnim = stateManager.createAtomicAnimation(
+                startState, NORMAL, config);
+        stateAnim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                mIsOverviewHidden = true;
+                maybeOverviewToHomeAnimComplete();
+            }
+        });
+        anim.play(stateAnim);
+        stateManager.setCurrentAnimation(anim, NORMAL);
+        anim.start();
+        recentsView.snapToPage(DEFAULT_PAGE, duration);
+    }
+
+    private void maybeOverviewToHomeAnimComplete() {
+        if (mIsHomeStaggeredAnimFinished && mIsOverviewHidden) {
+            mOnReachedHome.run();
+        }
+    }
+
+    /**
+     * Wrapper around StateAnimationConfig that doesn't allow interpolators to be set if they are
+     * already set. This ensures they aren't overridden before being used.
+     */
+    private static class UseFirstInterpolatorStateAnimConfig extends StateAnimationConfig {
+        @Override
+        public void setInterpolator(int animId, Interpolator interpolator) {
+            if (mInterpolators[animId] == null || interpolator == null) {
+                super.setInterpolator(animId, interpolator);
+            }
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ProtoTracer.java b/quickstep/src/com/android/quickstep/util/ProtoTracer.java
new file mode 100644
index 0000000..ef9586d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ProtoTracer.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_H_VALUE;
+import static com.android.launcher3.tracing.LauncherTraceFileProto.MagicNumber.MAGIC_NUMBER_L_VALUE;
+
+import android.content.Context;
+import android.os.SystemClock;
+
+import android.os.Trace;
+import com.android.launcher3.tracing.LauncherTraceProto;
+import com.android.launcher3.tracing.LauncherTraceEntryProto;
+import com.android.launcher3.tracing.LauncherTraceFileProto;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.shared.tracing.FrameProtoTracer;
+import com.android.systemui.shared.tracing.FrameProtoTracer.ProtoTraceParams;
+import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.google.protobuf.MessageLite;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Queue;
+
+
+/**
+ * Controller for coordinating winscope proto tracing.
+ */
+public class ProtoTracer implements ProtoTraceParams<MessageLite.Builder,
+        LauncherTraceFileProto.Builder, LauncherTraceEntryProto.Builder,
+                LauncherTraceProto.Builder> {
+
+    public static final MainThreadInitializedObject<ProtoTracer> INSTANCE =
+            new MainThreadInitializedObject<>(ProtoTracer::new);
+
+    private static final String TAG = "ProtoTracer";
+    private static final long MAGIC_NUMBER_VALUE =
+            ((long) MAGIC_NUMBER_H_VALUE << 32) | MAGIC_NUMBER_L_VALUE;
+
+    private final Context mContext;
+    private final FrameProtoTracer<MessageLite.Builder, LauncherTraceFileProto.Builder,
+        LauncherTraceEntryProto.Builder, LauncherTraceProto.Builder> mProtoTracer;
+
+    public ProtoTracer(Context context) {
+        mContext = context;
+        mProtoTracer = new FrameProtoTracer<>(this);
+    }
+
+    @Override
+    public File getTraceFile() {
+        return new File(mContext.getFilesDir(), "launcher_trace.pb");
+    }
+
+    @Override
+    public LauncherTraceFileProto.Builder getEncapsulatingTraceProto() {
+        return LauncherTraceFileProto.newBuilder();
+    }
+
+    @Override
+    public LauncherTraceEntryProto.Builder updateBufferProto(
+            LauncherTraceEntryProto.Builder reuseObj,
+            ArrayList<ProtoTraceable<LauncherTraceProto.Builder>> traceables) {
+        Trace.beginSection("ProtoTracer.updateBufferProto");
+        LauncherTraceEntryProto.Builder proto = LauncherTraceEntryProto.newBuilder();
+        proto.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        LauncherTraceProto.Builder launcherProto = LauncherTraceProto.newBuilder();
+        for (ProtoTraceable t : traceables) {
+            t.writeToProto(launcherProto);
+        }
+        proto.setLauncher(launcherProto);
+        Trace.endSection();
+        return proto;
+    }
+
+    @Override
+    public byte[] serializeEncapsulatingProto(LauncherTraceFileProto.Builder encapsulatingProto,
+            Queue<LauncherTraceEntryProto.Builder> buffer) {
+        Trace.beginSection("ProtoTracer.serializeEncapsulatingProto");
+        encapsulatingProto.setMagicNumber(MAGIC_NUMBER_VALUE);
+        for (LauncherTraceEntryProto.Builder entry : buffer) {
+            encapsulatingProto.addEntry(entry);
+        }
+        byte[] bytes = encapsulatingProto.build().toByteArray();
+        Trace.endSection();
+        return bytes;
+    }
+
+    @Override
+    public byte[] getProtoBytes(MessageLite.Builder proto) {
+        return proto.build().toByteArray();
+    }
+
+    @Override
+    public int getProtoSize(MessageLite.Builder proto) {
+        return proto.build().getSerializedSize();
+    }
+
+    public void start() {
+        mProtoTracer.start();
+    }
+
+    public void stop() {
+        mProtoTracer.stop();
+    }
+
+    public void add(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
+        mProtoTracer.add(traceable);
+    }
+
+    public void remove(ProtoTraceable<LauncherTraceProto.Builder> traceable) {
+        mProtoTracer.remove(traceable);
+    }
+
+    public void scheduleFrameUpdate() {
+        mProtoTracer.scheduleFrameUpdate();
+    }
+
+    public void update() {
+        mProtoTracer.update();
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 9ed2bbe..b10adb4 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -21,19 +21,18 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.SharedPreferences;
 
-import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.views.AllAppsEduView;
@@ -41,9 +40,9 @@
 /**
  * Extends {@link OnboardingPrefs} for quickstep-specific onboarding data.
  */
-public class QuickstepOnboardingPrefs extends OnboardingPrefs<BaseQuickstepLauncher> {
+public class QuickstepOnboardingPrefs extends OnboardingPrefs<QuickstepLauncher> {
 
-    public QuickstepOnboardingPrefs(BaseQuickstepLauncher launcher, SharedPreferences sharedPrefs) {
+    public QuickstepOnboardingPrefs(QuickstepLauncher launcher, SharedPreferences sharedPrefs) {
         super(launcher, sharedPrefs);
 
         StateManager<LauncherState> stateManager = launcher.getStateManager();
@@ -66,8 +65,7 @@
         }
 
         boolean shelfBounceSeen = getBoolean(SHELF_BOUNCE_SEEN);
-        if (!shelfBounceSeen && ENABLE_OVERVIEW_ACTIONS.get()
-                && removeShelfFromOverview(launcher)) {
+        if (!shelfBounceSeen && removeShelfFromOverview(launcher)) {
             // There's no shelf in overview, so don't bounce it (can't get to all apps anyway).
             shelfBounceSeen = true;
             mSharedPrefs.edit().putBoolean(SHELF_BOUNCE_SEEN, shelfBounceSeen).apply();
@@ -87,20 +85,6 @@
             });
         }
 
-        if (!hasReachedMaxCount(ALL_APPS_COUNT)) {
-            stateManager.addStateListener(new StateListener<LauncherState>() {
-                @Override
-                public void onStateTransitionComplete(LauncherState finalState) {
-                    if (finalState == ALL_APPS) {
-                        if (incrementEventCount(ALL_APPS_COUNT)) {
-                            stateManager.removeStateListener(this);
-                            mLauncher.getScrimView().updateDragHandleVisibility();
-                        }
-                    }
-                }
-            });
-        }
-
         if (FeatureFlags.ENABLE_HYBRID_HOTSEAT.get() && !hasReachedMaxCount(
                 HOTSEAT_DISCOVERY_TIP_COUNT)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java b/quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
rename to quickstep/src/com/android/quickstep/util/RecentsAtomicAnimationFactory.java
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index 81d24d7..df9b0cf 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -71,7 +71,7 @@
 public final class RecentsOrientedState implements SharedPreferences.OnSharedPreferenceChangeListener {
 
     private static final String TAG = "RecentsOrientedState";
-    private static final boolean DEBUG = true;
+    private static final boolean DEBUG = false;
 
     private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
         @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java b/quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/RectFSpringAnim.java
rename to quickstep/src/com/android/quickstep/util/RectFSpringAnim.java
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
index a770e8e..176478f 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenBounds.java
@@ -32,7 +32,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.WindowBounds;
 
 import java.util.ArrayList;
@@ -77,7 +77,7 @@
 
         WindowBounds bounds = new WindowBounds(wm.getBounds(),
                 new Rect(insets.left, insets.top, insets.right, insets.bottom));
-        int rotation = DefaultDisplay.INSTANCE.get(context).getInfo().rotation;
+        int rotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
         int halfDividerSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
rename to quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java b/quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/SurfaceTransactionApplier.java
rename to quickstep/src/com/android/quickstep/util/SurfaceTransactionApplier.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java b/quickstep/src/com/android/quickstep/util/TaskCornerRadius.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskCornerRadius.java
rename to quickstep/src/com/android/quickstep/util/TaskCornerRadius.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
rename to quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 2805299..903e87a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -32,7 +32,6 @@
 import android.util.IntProperty;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -73,6 +72,8 @@
     private final BaseActivityInterface mSizeStrategy;
 
     private final Rect mTaskRect = new Rect();
+    private float mOffsetY;
+    private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
 
@@ -92,8 +93,8 @@
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
     public final AnimatedFloat fullScreenProgress = new AnimatedFloat();
+    public final AnimatedFloat recentsViewSecondaryTranslation = new AnimatedFloat();
     private final ScrollState mScrollState = new ScrollState();
-    private final int mPageSpacing;
 
     // Cached calculations
     private boolean mLayoutValid = false;
@@ -107,7 +108,6 @@
         mOrientationState.setGestureActive(true);
 
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
-        mPageSpacing = context.getResources().getDimensionPixelSize(R.dimen.recents_page_spacing);
     }
 
     /**
@@ -178,8 +178,16 @@
         }
     }
 
+    public void setOffsetY(float offsetY) {
+        mOffsetY = offsetY;
+    }
+
+    public void setDrawsBelowRecents(boolean drawsBelowRecents) {
+        mDrawsBelowRecents = drawsBelowRecents;
+    }
+
     /**
-     * Adds animation for all the components corresponding to transition from an app to overview
+     * Adds animation for all the components corresponding to transition from an app to overview.
      */
     public void addAppToOverviewAnim(PendingAnimation pa, TimeInterpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
@@ -187,6 +195,14 @@
     }
 
     /**
+     * Adds animation for all the components corresponding to transition from overview to the app.
+     */
+    public void addOverviewToAppAnim(PendingAnimation pa, TimeInterpolator interpolator) {
+        pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 0, 1, interpolator);
+        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, 1, getFullScreenScale(), interpolator);
+    }
+
+    /**
      * Returns the current clipped/visible window bounds in the window coordinate space
      */
     public RectF getCurrentCropRect() {
@@ -262,7 +278,8 @@
             int start = mOrientationState.getOrientationHandler()
                     .getPrimaryValue(mTaskRect.left, mTaskRect.top);
             mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
-            mScrollState.updateInterpolation(start, mPageSpacing);
+            mScrollState.pageParentScale = recentsViewScale.value;
+            mScrollState.updateInterpolation(start);
             mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
         }
 
@@ -281,13 +298,15 @@
         mMatrix.postScale(scale, scale);
 
         // Apply TaskView matrix: translate, scale, scroll
-        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY);
         mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
         mOrientationState.getOrientationHandler().set(
                 mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
 
-        // Apply recensView matrix
+        // Apply RecentsView matrix
         mMatrix.postScale(recentsViewScale.value, recentsViewScale.value, mPivot.x, mPivot.y);
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                recentsViewSecondaryTranslation.value);
         applyWindowToHomeRotation(mMatrix);
 
         // Crop rect is the inverse of thumbnail matrix
@@ -307,7 +326,9 @@
                 .withCornerRadius(getCurrentCornerRadius());
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && params.getRecentsSurface() != null) {
-            builder.withRelativeLayerTo(params.getRecentsSurface(), Integer.MAX_VALUE);
+            // 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/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TransformParams.java
rename to quickstep/src/com/android/quickstep/util/TransformParams.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
similarity index 95%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
rename to quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
index 29b9558..7bbde30 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
+++ b/quickstep/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -21,13 +21,14 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 
 import android.content.Context;
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
 /**
@@ -50,7 +51,8 @@
             NavBarPosition navBarPosition, Runnable onInterceptTouch,
             OnSwipeUpListener onSwipeUp) {
         mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
-        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+        mMinFlingVelocity = context.getResources().getDimension(
+                R.dimen.quickstep_fling_threshold_speed);
         mNavBarPosition = navBarPosition;
         mDisableHorizontalSwipe = disableHorizontalSwipe;
         mOnInterceptTouch = onInterceptTouch;
@@ -130,7 +132,7 @@
     }
 
     private void onGestureEnd(MotionEvent ev) {
-        mVelocityTracker.computeCurrentVelocity(1000);
+        mVelocityTracker.computeCurrentVelocity(PX_PER_MS);
         float velocityX = mVelocityTracker.getXVelocity();
         float velocityY = mVelocityTracker.getYVelocity();
         float velocity = mNavBarPosition.isRightEdge()
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
rename to quickstep/src/com/android/quickstep/views/AllAppsEduView.java
index c06dd9c..97a239c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/AllAppsEduView.java
+++ b/quickstep/src/com/android/quickstep/views/AllAppsEduView.java
@@ -105,11 +105,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ALL_APPS_EDU) != 0;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
similarity index 93%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
rename to quickstep/src/com/android/quickstep/views/ClearAllButton.java
index fd74357..0837300 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/ClearAllButton.java
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
@@ -45,12 +45,15 @@
     private float mVisibilityAlpha = 1;
 
     private boolean mIsRtl;
+    private final float mOriginalTranslationX, mOriginalTranslationY;
 
     private int mScrollOffset;
 
     public ClearAllButton(Context context, AttributeSet attrs) {
         super(context, attrs);
         mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+        mOriginalTranslationX = getTranslationX();
+        mOriginalTranslationY = getTranslationY();
     }
 
     @Override
@@ -99,7 +102,8 @@
 
         float shift = Math.min(scrollState.scrollFromEdge, orientationSize);
         float translation = mIsRtl ? (mScrollOffset - shift) : (mScrollOffset + shift);
-        orientationHandler.setPrimaryAndResetSecondaryTranslate(this, translation);
+        orientationHandler.setPrimaryAndResetSecondaryTranslate(
+                this, translation, mOriginalTranslationX, mOriginalTranslationY);
         mScrollAlpha = 1 - shift / orientationSize;
         updateAlpha();
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/DigitalWellBeingToast.java
rename to quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java b/quickstep/src/com/android/quickstep/views/IconView.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/IconView.java
rename to quickstep/src/com/android/quickstep/views/IconView.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
similarity index 76%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
rename to quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 1034234..b338bd0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.views;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -39,14 +38,12 @@
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
-import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SysUINavigationMode;
-import com.android.quickstep.util.TransformParams;
+import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
 
@@ -57,8 +54,6 @@
 public class LauncherRecentsView extends RecentsView<BaseQuickstepLauncher>
         implements StateListener<LauncherState> {
 
-    private final TransformParams mTransformParams = new TransformParams();
-
     private RecentsExtraCard mRecentsExtraCardPlugin;
     private RecentsExtraViewContainer mRecentsExtraViewContainer;
     private PluginListener<RecentsExtraCard> mRecentsExtraCardPluginListener =
@@ -99,23 +94,14 @@
 
     @Override
     public void startHome() {
+        Runnable onReachedHome = () -> mActivity.getStateManager().goToState(NORMAL, false);
+        OverviewToHomeAnim overviewToHomeAnim = new OverviewToHomeAnim(mActivity, onReachedHome);
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             switchToScreenshot(null,
                     () -> finishRecentsAnimation(true /* toRecents */,
-                            () -> mActivity.getStateManager().goToState(NORMAL)));
+                            () -> overviewToHomeAnim.animateWithVelocity(0)));
         } else {
-            mActivity.getStateManager().goToState(NORMAL);
-        }
-    }
-
-    @Override
-    public void setTranslationY(float translationY) {
-        super.setTranslationY(translationY);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            LauncherState state = mActivity.getStateManager().getState();
-            if (state == OVERVIEW || state == ALL_APPS) {
-                redrawLiveTile(false);
-            }
+            overviewToHomeAnim.animateWithVelocity(0);
         }
     }
 
@@ -141,29 +127,10 @@
         }
         anim.play(ObjectAnimator.ofFloat(
                 mActivity.getAllAppsController(), ALL_APPS_PROGRESS, allAppsProgressOffscreen));
-
-        ObjectAnimator dragHandleAnim = ObjectAnimator.ofInt(
-                mActivity.getScrimView(), ScrimView.DRAG_HANDLE_ALPHA, 0);
-        dragHandleAnim.setInterpolator(Interpolators.ACCEL_2);
-        anim.play(dragHandleAnim);
-
         return anim;
     }
 
     @Override
-    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (tv.isRunningTask()) {
-                mTransformParams.setProgress(1 - progress)
-                        .setSyncTransactionApplier(mSyncTransactionApplier);
-                // TODO: Revisit live tiles
-            } else {
-                redrawLiveTile(true);
-            }
-        }
-    }
-
-    @Override
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             mActivity.getStateManager().goToState(NORMAL, false /* animate */);
@@ -175,46 +142,6 @@
     }
 
     @Override
-    public void scrollTo(int x, int y) {
-        super.scrollTo(x, y);
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
-            redrawLiveTile(true);
-        }
-    }
-
-    @Override
-    public TransformParams getLiveTileParams(
-            boolean mightNeedToRefill) {
-        if (!mEnableDrawingLiveTile || mRecentsAnimationController == null
-                || mRecentsAnimationTargets == null) {
-            return null;
-        }
-        TaskView taskView = getRunningTaskView();
-        if (taskView != null) {
-            taskView.getThumbnail().getGlobalVisibleRect(mTempRect);
-            int offsetX = (int) (mTaskWidth * taskView.getScaleX() * getScaleX()
-                    - mTempRect.width());
-            int offsetY = (int) (mTaskHeight * taskView.getScaleY() * getScaleY()
-                    - mTempRect.height());
-            if (((mCurrentPage != 0) || mightNeedToRefill) && offsetX > 0) {
-                if (mTempRect.left - offsetX < 0) {
-                    mTempRect.left -= offsetX;
-                } else {
-                    mTempRect.right += offsetX;
-                }
-            }
-            if (mightNeedToRefill && offsetY > 0) {
-                mTempRect.top -= offsetY;
-            }
-            mTransformParams.setProgress(1f)
-                    .setTargetAlpha(taskView.getAlpha())
-                    .setSyncTransactionApplier(mSyncTransactionApplier)
-                    .setTargetSet(mRecentsAnimationTargets);
-        }
-        return mTransformParams;
-    }
-
-    @Override
     public void reset() {
         super.reset();
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
rename to quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index 30c9f77..c6c2d7e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -40,14 +40,13 @@
     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 RectF mCurrentRect;
     private float mCornerRadius;
     private Drawable mIcon;
     private Animator mIconAnimator;
 
-    private boolean mDrawEnabled = true;
     private float mIconAnimationProgress = 0f;
     private boolean mIsAttached;
 
@@ -58,7 +57,7 @@
     public void update(RectF currentRect, float cornerRadius) {
         invalidateSelf();
 
-        mCurrentRect = currentRect;
+        mCurrentRect.set(currentRect);
         mCornerRadius = cornerRadius;
 
         mCurrentRect.roundOut(mBoundsRect);
@@ -93,16 +92,9 @@
         return mIconAnimationProgress;
     }
 
-    public void setDrawEnabled(boolean drawEnabled) {
-        if (mDrawEnabled != drawEnabled) {
-            mDrawEnabled = drawEnabled;
-            invalidateSelf();
-        }
-    }
-
     @Override
     public void draw(Canvas canvas) {
-        if (mCurrentRect != null && mDrawEnabled) {
+        if (mCurrentRect != null) {
             canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
             if (mIcon != null && mIconAnimationProgress > 0f) {
                 canvas.save();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
similarity index 92%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
rename to quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 79d57c5..2a6c9e9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SHARE;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
@@ -53,7 +52,6 @@
 
     @IntDef(flag = true, value = {
             HIDDEN_UNSUPPORTED_NAVIGATION,
-            HIDDEN_DISABLED_FEATURE,
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
             HIDDEN_GESTURE_RUNNING,
@@ -62,11 +60,10 @@
     public @interface ActionsHiddenFlags { }
 
     public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
-    public static final int HIDDEN_DISABLED_FEATURE = 1 << 1;
-    public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 2;
-    public static final int HIDDEN_NO_TASKS = 1 << 3;
-    public static final int HIDDEN_GESTURE_RUNNING = 1 << 4;
-    public static final int HIDDEN_NO_RECENTS = 1 << 5;
+    public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 1;
+    public static final int HIDDEN_NO_TASKS = 1 << 2;
+    public static final int HIDDEN_GESTURE_RUNNING = 1 << 3;
+    public static final int HIDDEN_NO_RECENTS = 1 << 4;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
@@ -105,6 +102,7 @@
     public OverviewActionsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr, 0);
         mMultiValueAlpha = new MultiValueAlpha(this, 4);
+        mMultiValueAlpha.setUpdateVisibility(true);
     }
 
     @Override
@@ -115,7 +113,7 @@
         findViewById(R.id.action_screenshot).setOnClickListener(this);
         if (ENABLE_OVERVIEW_SHARE.get()) {
             share.setVisibility(VISIBLE);
-            findViewById(R.id.share_space).setVisibility(VISIBLE);
+            findViewById(R.id.oav_three_button_space).setVisibility(VISIBLE);
         }
     }
 
@@ -144,7 +142,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        updateHiddenFlags(HIDDEN_DISABLED_FEATURE, !ENABLE_OVERVIEW_ACTIONS.get());
         updateHiddenFlags(HIDDEN_UNSUPPORTED_NAVIGATION, !removeShelfFromOverview(getContext()));
     }
 
@@ -168,7 +165,6 @@
         }
         boolean isHidden = mHiddenFlags != 0;
         mMultiValueAlpha.getProperty(INDEX_HIDDEN_FLAGS_ALPHA).setValue(isHidden ? 0 : 1);
-        setVisibility(isHidden ? INVISIBLE : VISIBLE);
     }
 
     /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
similarity index 100%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsExtraViewContainer.java
rename to quickstep/src/com/android/quickstep/views/RecentsExtraViewContainer.java
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
similarity index 86%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
rename to quickstep/src/com/android/quickstep/views/RecentsView.java
index 211a2ce..0362377 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -22,9 +22,9 @@
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
+import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -62,6 +62,7 @@
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -92,6 +93,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -107,13 +109,12 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.BaseActivityInterface;
@@ -122,6 +123,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RecentsModel.TaskVisualsChangeListener;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
@@ -129,6 +131,7 @@
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
 import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.util.TransformParams;
 import com.android.systemui.plugins.ResourceProvider;
 import com.android.systemui.shared.recents.IPinnedStackAnimationListener;
@@ -136,7 +139,6 @@
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.LauncherEventUtil;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 
@@ -209,15 +211,51 @@
                 }
             };
 
+    public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
+            new FloatProperty<RecentsView>("taskSecondaryTranslation") {
+                @Override
+                public void setValue(RecentsView recentsView, float v) {
+                    recentsView.setTaskViewsSecondaryTranslation(v);
+                }
+
+                @Override
+                public Float get(RecentsView recentsView) {
+                    return recentsView.mTaskViewsSecondaryTranslation;
+                }
+            };
+
+    /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
+    public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
+            new FloatProperty<RecentsView>("recentsScale") {
+                @Override
+                public void setValue(RecentsView view, float scale) {
+                    view.setScaleX(scale);
+                    view.setScaleY(scale);
+                    view.mLastComputedTaskPushOutDistance = null;
+                    view.updatePageOffsets();
+                    view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
+                }
+
+                @Override
+                public Float get(RecentsView view) {
+                    return view.getScaleX();
+                }
+            };
+
     protected RecentsOrientedState mOrientationState;
     protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
-    protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected SurfaceTransactionApplier mSyncTransactionApplier;
     protected int mTaskWidth;
     protected int mTaskHeight;
+    protected final TransformParams mLiveTileParams = new TransformParams();
+    protected final TaskViewSimulator mLiveTileTaskViewSimulator;
+    protected final Rect mLastComputedTaskSize = new Rect();
+    // How much a task that is directly offscreen will be pushed out due to RecentsView scale/pivot.
+    protected Float mLastComputedTaskPushOutDistance = null;
     protected boolean mEnableDrawingLiveTile = false;
     protected final Rect mTempRect = new Rect();
+    protected final RectF mTempRectF = new RectF();
     private final PointF mTempPointF = new PointF();
 
     private static final int DISMISS_TASK_DURATION = 300;
@@ -241,12 +279,15 @@
 
     private final ViewPool<TaskView> mTaskViewPool;
 
+    private final TaskOverlayFactory mTaskOverlayFactory;
+
     private boolean mDwbToastShown;
     protected boolean mDisallowScrollToClearAll;
     private boolean mOverlayEnabled;
     protected boolean mFreezeViewVisibility;
 
     private float mAdjacentPageOffset = 0;
+    private float mTaskViewsSecondaryTranslation = 0;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -393,7 +434,8 @@
         mActivity = BaseActivity.fromContext(context);
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
-        mOrientationState.setRecentsRotation(mActivity.getDisplay().getRotation());
+        final int rotation = mActivity.getDisplay().getRotation();
+        mOrientationState.setRecentsRotation(rotation);
 
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
@@ -427,8 +469,20 @@
         updateEmptyMessage();
         mOrientationHandler = mOrientationState.getOrientationHandler();
 
+        mTaskOverlayFactory = Overrides.getObject(
+                TaskOverlayFactory.class,
+                context.getApplicationContext(),
+                R.string.task_overlay_factory_class);
+
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
+
+        mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
+        mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+                getPagedViewOrientedState().getDisplayRotation());
+        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+        mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
     public OverScroller getScroller() {
@@ -513,6 +567,7 @@
         mActivity.addMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
+        mLiveTileParams.setSyncTransactionApplier(mSyncTransactionApplier);
         RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
         mIdp.addOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(mActivity);
@@ -520,6 +575,7 @@
                 mIPinnedStackAnimationListener);
         mOrientationState.initListeners();
         SplitScreenBounds.INSTANCE.addOnChangeListener(this);
+        mTaskOverlayFactory.initListeners();
     }
 
     @Override
@@ -530,12 +586,14 @@
         mActivity.removeMultiWindowModeChangedListener(mMultiWindowModeChangedListener);
         ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mTaskStackListener);
         mSyncTransactionApplier = null;
+        mLiveTileParams.setSyncTransactionApplier(null);
         RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
         mIdp.removeOnChangeListener(this);
         SystemUiProxy.INSTANCE.get(getContext()).setPinnedStackAnimationListener(null);
         SplitScreenBounds.INSTANCE.removeOnChangeListener(this);
         mIPinnedStackAnimationListener.setActivity(null);
         mOrientationState.destroyListeners();
+        mTaskOverlayFactory.removeListeners();
     }
 
     @Override
@@ -611,9 +669,6 @@
     public void onDigitalWellbeingToastShown() {
         if (!mDwbToastShown) {
             mDwbToastShown = true;
-            mActivity.getUserEventDispatcher().logActionTip(
-                    LauncherEventUtil.VISIBLE,
-                    LauncherLogProto.TipType.DWB_TOAST);
         }
     }
 
@@ -648,6 +703,13 @@
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
+
+        TaskView taskView = getCurrentPageTaskView();
+        if (taskView != null && taskView.offerTouchToChildren(ev)) {
+            // Keep consuming events to pass to delegate
+            return true;
+        }
+
         final int x = (int) ev.getX();
         final int y = (int) ev.getY();
         switch (ev.getAction()) {
@@ -806,6 +868,14 @@
                 taskView.setModalness(mTaskModalness);
             }
         }
+        if (ENABLE_QUICKSTEP_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.fullScreenProgress.value = 0;
+            mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
+            mLiveTileTaskViewSimulator.setOffsetY(0);
+        }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
         }
@@ -847,6 +917,7 @@
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         resetPaddingFromTaskSize();
+        mLiveTileTaskViewSimulator.setDp(mActivity.getDeviceProfile());
     }
 
     private void resetPaddingFromTaskSize() {
@@ -864,6 +935,7 @@
     public void getTaskSize(Rect outRect) {
         mSizeStrategy.calculateTaskSize(mActivity, mActivity.getDeviceProfile(), outRect,
                 mOrientationHandler);
+        mLastComputedTaskSize.set(outRect);
     }
 
     /** Gets the task size for modal state. */
@@ -888,6 +960,12 @@
 
         // Update the high res thumbnail loader state
         mModel.getThumbnailCache().getHighResLoadingState().setFlingingFast(isFlingingFast);
+
+        mLiveTileTaskViewSimulator.setScroll(getScrollOffset());
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
+                && mLiveTileParams.getTargetSet() != null) {
+            redrawLiveTile();
+        }
         return scrolling;
     }
 
@@ -905,8 +983,8 @@
         final int pageCount = getPageCount();
         for (int i = 0; i < pageCount; i++) {
             View page = getPageAt(i);
-            mScrollState.updateInterpolation(mOrientationHandler.getChildStartWithTranslation(page),
-                    mPageSpacing);
+            mScrollState.updateInterpolation(
+                    mOrientationHandler.getChildStartWithTranslation(page));
             ((PageCallbacks) page).onPageScroll(mScrollState);
         }
     }
@@ -995,7 +1073,7 @@
         mTaskListChangeId = -1;
 
         mRecentsAnimationController = null;
-        mRecentsAnimationTargets = null;
+        mLiveTileParams.setTargetSet(null);
 
         unloadVisibleTaskData();
         setCurrentPage(0);
@@ -1124,9 +1202,9 @@
     }
 
     /**
-     * Returns true if we should add a dummy taskView for the running task id
+     * Returns true if we should add a stub taskView for the running task id
      */
-    protected boolean shouldAddDummyTaskView(RunningTaskInfo runningTaskInfo) {
+    protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
         return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
     }
 
@@ -1137,7 +1215,7 @@
      * is called.  Also scrolls the view to this task.
      */
     public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
-        if (shouldAddDummyTaskView(runningTaskInfo)) {
+        if (shouldAddStubTaskView(runningTaskInfo)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView = mTaskViewPool.getView();
@@ -1328,10 +1406,14 @@
         /**
          * Updates linearInterpolation for the provided child position
          */
-        public void updateInterpolation(float childStart, int pageSpacing) {
-            float pageCenter = childStart + halfPageSize;
+        public void updateInterpolation(float childStart) {
+            float scaledHalfPageSize = halfPageSize / pageParentScale;
+            float pageCenter = childStart + scaledHalfPageSize;
             float distanceFromScreenCenter = screenCenter - pageCenter;
-            float distanceToReachEdge = halfScreenSize + halfPageSize + pageSpacing;
+            // How far the page has to move from the center to be offscreen, taking into account
+            // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position.
+            float distanceToReachEdge = halfScreenSize
+                    + scaledHalfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR);
             linearInterpolation = Math.min(1,
                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
         }
@@ -1354,7 +1436,7 @@
         FloatProperty<View> secondaryViewTranslate =
             mOrientationHandler.getSecondaryViewTranslate();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
-        int verticalFactor = mOrientationHandler.getTaskDismissDirectionFactor();
+        int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
         ResourceProvider rp = DynamicResource.provider(mActivity);
         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
@@ -1369,8 +1451,6 @@
         if (taskView.getTask() != null) {
             ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
             ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
-            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                    endState.logAction, Direction.UP, index, compKey);
             mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
                     .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
         }
@@ -1446,6 +1526,13 @@
             anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) {
+            anim.addOnFrameCallback(() -> {
+                mLiveTileTaskViewSimulator.setOffsetY(taskView.getTranslationY());
+                redrawLiveTile();
+            });
+        }
+
         // Add a tiny bit of translation Z, so that it draws on top of other views
         if (animateTaskView) {
             taskView.setTranslationZ(0.1f);
@@ -1657,9 +1744,17 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        if (mOrientationState.setRecentsRotation(mActivity.getDisplay().getRotation())) {
+        final int rotation = mActivity.getDisplay().getRotation();
+        if (mOrientationState.setRecentsRotation(rotation)) {
             updateOrientationHandler();
         }
+        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+        // If overview is in modal state when rotate, reset it to overview state without running
+        // animation.
+        if (mActivity.isInState(OVERVIEW_MODAL_TASK)) {
+            mActivity.getStateManager().goToState(LauncherState.OVERVIEW, false);
+            resetModalVisuals();
+        }
     }
 
     public void setLayoutRotation(int touchRotation, int displayRotation) {
@@ -1688,6 +1783,8 @@
         requestLayout();
         // Reapply the current page to update page scrolls.
         setCurrentPage(mCurrentPage);
+        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
+                getPagedViewOrientedState().getDisplayRotation());
     }
 
     public RecentsOrientedState getPagedViewOrientedState() {
@@ -1769,14 +1866,15 @@
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
         setTaskModalness(mTaskModalness);
+        mLastComputedTaskPushOutDistance = null;
         updatePageOffsets();
         setImportantForAccessibility(isModal() ? IMPORTANT_FOR_ACCESSIBILITY_NO
                 : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
     }
 
     private void updatePageOffsets() {
-        float offset = mAdjacentPageOffset * getWidth();
-        float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness) * getWidth();
+        float offset = mAdjacentPageOffset;
+        float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
         if (mIsRtl) {
             offset = -offset;
             modalOffset = -modalOffset;
@@ -1785,19 +1883,100 @@
 
         TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
                 ? null : getTaskView(mRunningTaskId);
-        int midPoint = runningTask == null ? -1 : indexOfChild(runningTask);
-        int currentPage = getCurrentPage();
+        int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+        int modalMidpoint = getCurrentPage();
+
+        float midpointOffsetSize = 0;
+        float leftOffsetSize = midpoint - 1 >= 0
+                ? -getOffsetSize(midpoint - 1, midpoint, offset)
+                : 0;
+        float rightOffsetSize = midpoint + 1 < count
+                ? getOffsetSize(midpoint + 1, midpoint, offset)
+                : 0;
+
+        float modalMidpointOffsetSize = 0;
+        float modalLeftOffsetSize = modalMidpoint - 1 >= 0
+                ? -getOffsetSize(modalMidpoint - 1, modalMidpoint, modalOffset)
+                : 0;
+        float modalRightOffsetSize = modalMidpoint + 1 < count
+                ? getOffsetSize(modalMidpoint + 1, modalMidpoint, modalOffset)
+                : 0;
 
         for (int i = 0; i < count; i++) {
-            float translation = i == midPoint ? 0 : (i < midPoint ? -offset : offset);
-            float modalTranslation =
-                    i == currentPage ? 0 : (i < currentPage ? -modalOffset : modalOffset);
-            getChildAt(i).setTranslationX(translation + modalTranslation);
+            float translation = i == midpoint
+                    ? midpointOffsetSize
+                    : i < midpoint
+                            ? leftOffsetSize
+                            : rightOffsetSize;
+            float modalTranslation = i == modalMidpoint
+                    ? modalMidpointOffsetSize
+                    : i < modalMidpoint
+                            ? modalLeftOffsetSize
+                            : modalRightOffsetSize;
+            float totalTranslation = translation + modalTranslation;
+            mOrientationHandler.getPrimaryViewTranslate().set(getChildAt(i),
+                    totalTranslation * mOrientationHandler.getPrimaryTranslationDirectionFactor());
         }
         updateCurveProperties();
     }
 
     /**
+     * Computes the distance to offset the given child such that it is completely offscreen when
+     * translating away from the given midpoint.
+     * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
+     */
+    private float getOffsetSize(int childIndex, int midpointIndex, float offsetProgress) {
+        if (offsetProgress == 0) {
+            // Don't bother calculating everything below if we won't offset anyway.
+            return 0;
+        }
+        // First, get the position of the task relative to the midpoint. If there is no midpoint
+        // then we just use the normal (centered) task position.
+        mTempRectF.set(mLastComputedTaskSize);
+        RectF taskPosition = mTempRectF;
+        float desiredLeft = getWidth();
+        float distanceToOffscreen = desiredLeft - taskPosition.left;
+        // Used to calculate the scale of the task view based on its new offset.
+        float centerToOffscreenProgress = Math.abs(offsetProgress);
+        if (midpointIndex > -1) {
+            // When there is a midpoint reference task, adjacent tasks have less distance to travel
+            // to reach offscreen. Offset the task position to the task's starting point.
+            View child = getChildAt(childIndex);
+            View midpointChild = getChildAt(midpointIndex);
+            int distanceFromMidpoint = Math.abs(mOrientationHandler.getChildStart(child)
+                    - mOrientationHandler.getChildStart(midpointChild)
+                    + getDisplacementFromScreenCenter(midpointIndex));
+            taskPosition.offset(distanceFromMidpoint, 0);
+            centerToOffscreenProgress = Utilities.mapRange(centerToOffscreenProgress,
+                    distanceFromMidpoint / distanceToOffscreen, 1);
+        }
+        // Find the task's scale based on its offscreen progress, then see how far it still needs to
+        // move to be completely offscreen.
+        Utilities.scaleRectFAboutCenter(taskPosition,
+                TaskView.getCurveScaleForInterpolation(centerToOffscreenProgress));
+        distanceToOffscreen = desiredLeft - taskPosition.left;
+        // Finally, we need to account for RecentsView scale, because it moves tasks based on its
+        // pivot. To do this, we move the task position to where it would be offscreen at scale = 1
+        // (computed above), then we apply the scale via getMatrix() to determine how much that
+        // moves the task from its desired position, and adjust the computed distance accordingly.
+        if (mLastComputedTaskPushOutDistance == null) {
+            taskPosition.offsetTo(desiredLeft, 0);
+            getMatrix().mapRect(taskPosition);
+            mLastComputedTaskPushOutDistance = (taskPosition.left - desiredLeft) / getScaleX();
+        }
+        distanceToOffscreen -= mLastComputedTaskPushOutDistance;
+        return distanceToOffscreen * offsetProgress;
+    }
+
+    private void setTaskViewsSecondaryTranslation(float translation) {
+        mTaskViewsSecondaryTranslation = translation;
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView task = getTaskViewAt(i);
+            mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
+        }
+    }
+
+    /**
      * TODO: Do not assume motion across X axis for adjacent page
      */
     public float getPageOffsetScale() {
@@ -1895,7 +2074,7 @@
         float toScale = getMaxScaleForFullScreen();
         if (launchingCenterTask) {
             RecentsView recentsView = tv.getRecentsView();
-            anim.play(ObjectAnimator.ofFloat(recentsView, SCALE_PROPERTY, toScale));
+            anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale));
             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -1945,8 +2124,6 @@
                             ? targetSysUiFlags
                             : 0);
 
-            onTaskLaunchAnimationUpdate(animator.getAnimatedFraction(), tv);
-
             // Passing the threshold from taskview to fullscreen app will vibrate
             final boolean passed = animator.getAnimatedFraction() >=
                     SUCCESS_TRANSITION_PROGRESS;
@@ -1970,6 +2147,10 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
+            mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+        }
         mPendingAnimation.addEndListener((endState) -> {
             if (endState.isSuccess) {
                 Consumer<Boolean> onLaunchResult = (result) -> {
@@ -1981,9 +2162,6 @@
                 tv.launchTask(false, onLaunchResult, getHandler());
                 Task task = tv.getTask();
                 if (task != null) {
-                    mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                            endState.logAction, Direction.DOWN, indexOfChild(tv),
-                            TaskUtils.getLaunchComponentKeyForTask(task.key));
                     mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
                             .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
                 }
@@ -1995,9 +2173,6 @@
         return mPendingAnimation;
     }
 
-    protected void onTaskLaunchAnimationUpdate(float progress, TaskView tv) {
-    }
-
     protected void onTaskLaunchAnimationEnd(boolean success) {
         if (success) {
             resetTaskVisuals();
@@ -2064,13 +2239,23 @@
         mEnableDrawingLiveTile = enableDrawingLiveTile;
     }
 
-    public void redrawLiveTile(boolean mightNeedToRefill) { }
+    public void redrawLiveTile() {
+        mLiveTileTaskViewSimulator.apply(mLiveTileParams);
+    }
+
+    public TaskViewSimulator getLiveTileTaskViewSimulator() {
+        return mLiveTileTaskViewSimulator;
+    }
 
     // TODO: To be removed in a follow up CL
     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
             RecentsAnimationTargets recentsAnimationTargets) {
         mRecentsAnimationController = recentsAnimationController;
-        mRecentsAnimationTargets = recentsAnimationTargets;
+        if (recentsAnimationTargets != null && recentsAnimationTargets.apps.length > 0) {
+            mLiveTileTaskViewSimulator.setPreview(
+                    recentsAnimationTargets.apps[recentsAnimationTargets.apps.length - 1]);
+            mLiveTileParams.setTargetSet(recentsAnimationTargets);
+        }
     }
 
     public void setLiveTileOverlayAttached(boolean liveTileOverlayAttached) {
@@ -2094,13 +2279,13 @@
         mRecentsAnimationController.finish(toRecents, () -> {
             if (onFinishComplete != null) {
                 onFinishComplete.run();
-                // After we finish the recents animation, the current task id should be correctly
-                // reset so that when the task is launched from Overview later, it goes through the
-                // flow of starting a new task instead of finishing recents animation to app. A
-                // typical example of this is (1) user swipes up from app to Overview (2) user
-                // taps on QSB (3) user goes back to Overview and launch the most recent task.
-                setCurrentTask(-1);
             }
+            // After we finish the recents animation, the current task id should be correctly
+            // reset so that when the task is launched from Overview later, it goes through the
+            // flow of starting a new task instead of finishing recents animation to app. A
+            // typical example of this is (1) user swipes up from app to Overview (2) user
+            // taps on QSB (3) user goes back to Overview and launch the most recent task.
+            setCurrentTask(-1);
         });
     }
 
@@ -2174,7 +2359,14 @@
         if (pageIndex == -1) {
             return 0;
         }
-        return getScrollForPage(pageIndex) - mOrientationHandler.getPrimaryScroll(this);
+        // Unbound the scroll (due to overscroll) if the adjacent tasks are offset away from it.
+        // This allows the page to move freely, given there's no visual indication why it shouldn't.
+        int boundedScroll = mOrientationHandler.getPrimaryScroll(this);
+        int unboundedScroll = getUnboundedScroll();
+        float unboundedProgress = mAdjacentPageOffset;
+        int scroll = Math.round(unboundedScroll * unboundedProgress
+                + boundedScroll * (1 - unboundedProgress));
+        return getScrollForPage(pageIndex) - scroll;
     }
 
     public Consumer<MotionEvent> getEventDispatcher(float navbarRotation) {
@@ -2206,11 +2398,6 @@
         };
     }
 
-    public TransformParams getLiveTileParams(
-            boolean mightNeedToRefill) {
-        return null;
-    }
-
     private void updateEnabledOverlays() {
         int overlayEnabledPage = mOverlayEnabled ? getNextPage() : -1;
         int taskCount = getTaskViewCount();
@@ -2226,7 +2413,19 @@
         }
     }
 
-    /** If it's in the live tile mode, switch the running task into screenshot mode. */
+    /**
+     * Switch the current running task view to static snapshot mode,
+     * capturing the snapshot at the same time.
+     */
+    public void switchToScreenshot(Runnable onFinishRunnable) {
+        switchToScreenshot(mRunningTaskId == -1 ? null
+                : mRecentsAnimationController.screenshotTask(mRunningTaskId), onFinishRunnable);
+    }
+
+    /**
+     * Switch the current running task view to static snapshot mode, using the
+     * provided thumbnail data as the snapshot.
+     */
     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
         TaskView taskView = getRunningTaskView();
         if (taskView != null) {
@@ -2276,6 +2475,10 @@
      */
     public void setModalStateEnabled(boolean isModalState) { }
 
+    public TaskOverlayFactory getTaskOverlayFactory() {
+        return mTaskOverlayFactory;
+    }
+
     public BaseActivityInterface getSizeStrategy() {
         return mSizeStrategy;
     }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 19e278b..e6613eb 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -16,9 +16,7 @@
 package com.android.quickstep.views;
 
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -38,13 +36,10 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.states.OverviewState;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 import com.android.quickstep.SysUINavigationMode;
@@ -77,15 +72,11 @@
     private final float mRadius;
     private final int mMaxScrimAlpha;
     private final Paint mPaint;
-    private final OnboardingPrefs mOnboardingPrefs;
 
     // Mid point where the alpha changes
     private int mMidAlpha;
     private float mMidProgress;
 
-    // The progress at which the drag handle starts moving up with the shelf.
-    private float mDragHandleProgress;
-
     private Interpolator mBeforeMidProgressColorInterpolator = ACCEL;
     private Interpolator mAfterMidProgressColorInterpolator = ACCEL;
 
@@ -103,7 +94,6 @@
     private boolean mRemainingScreenPathValid = false;
 
     private Mode mSysUINavigationMode;
-    private boolean mIsTwoZoneSwipeModel;
 
     public ShelfScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -112,7 +102,6 @@
         mEndAlpha = Color.alpha(mEndScrim);
         mRadius = BOTTOM_CORNER_RADIUS_RATIO * Themes.getDialogCornerRadius(context);
         mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mOnboardingPrefs = mLauncher.getOnboardingPrefs();
 
         // Just assume the easiest UI for now, until we have the proper layout information.
         mDrawingFlatColor = true;
@@ -145,11 +134,9 @@
             // Show the shelf more quickly before reaching overview progress.
             mBeforeMidProgressColorInterpolator = ACCEL_2;
             mAfterMidProgressColorInterpolator = ACCEL;
-            mIsTwoZoneSwipeModel = FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get();
         } else {
             mBeforeMidProgressColorInterpolator = ACCEL;
             mAfterMidProgressColorInterpolator = Interpolators.clampToProgress(ACCEL, 0.5f, 1f);
-            mIsTwoZoneSwipeModel = false;
         }
     }
 
@@ -164,9 +151,7 @@
 
             Context context = getContext();
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
-                mDragHandleProgress = 1;
-                if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
-                        && SysUINavigationMode.removeShelfFromOverview(context)) {
+                if (SysUINavigationMode.removeShelfFromOverview(context)) {
                     // Fade in all apps background quickly to distinguish from swiping from nav bar.
                     mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
                     mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
@@ -182,29 +167,22 @@
                         + hotseatPadding.bottom + hotseatPadding.top;
                 float dragHandleTop =
                         Math.min(hotseatSize, LayoutUtils.getDefaultSwipeHeight(context, dp));
-                mDragHandleProgress =  1 - (dragHandleTop / mShiftRange);
             }
-            mTopOffset = dp.getInsets().top - mDragHandleSize.y;
+            mTopOffset = dp.getInsets().top;
             mShelfTopAtThreshold = mShiftRange * SCRIM_CATCHUP_THRESHOLD + mTopOffset;
         }
         updateColors();
         updateSysUiColors();
-        updateDragHandleAlpha();
         invalidate();
     }
 
     @Override
     public void updateColors() {
         super.updateColors();
-        mDragHandleOffset = 0;
         if (mDrawingFlatColor) {
             return;
         }
 
-        if (mProgress < mDragHandleProgress) {
-            mDragHandleOffset = mShiftRange * (mDragHandleProgress - mProgress);
-        }
-
         if (mProgress >= SCRIM_CATCHUP_THRESHOLD) {
             mShelfTop = mShiftRange * mProgress + mTopOffset;
         } else {
@@ -215,13 +193,6 @@
         if (mProgress >= 1) {
             mRemainingScreenColor = 0;
             mShelfColor = 0;
-            LauncherState state = mLauncher.getStateManager().getState();
-            if (mSysUINavigationMode == Mode.NO_BUTTON
-                    && (state == BACKGROUND_APP || state == QUICK_SWITCH)
-                    && mLauncher.getShelfPeekAnim().isPeeking()) {
-                // Show the shelf background when peeking during swipe up.
-                mShelfColor = setColorAlphaBound(mEndScrim, mMidAlpha);
-            }
         } else if (mProgress >= mMidProgress) {
             mRemainingScreenColor = 0;
 
@@ -259,19 +230,7 @@
     }
 
     @Override
-    protected boolean shouldDragHandleBeVisible() {
-        boolean needsAllAppsEdu = mIsTwoZoneSwipeModel
-                && !mOnboardingPrefs.hasReachedMaxCount(OnboardingPrefs.ALL_APPS_COUNT);
-        return needsAllAppsEdu || super.shouldDragHandleBeVisible();
-    }
-
-    @Override
     protected void onDraw(Canvas canvas) {
-        drawBackground(canvas);
-        drawDragHandle(canvas);
-    }
-
-    private void drawBackground(Canvas canvas) {
         if (mDrawingFlatColor) {
             if (mCurrentFlatColor != 0) {
                 canvas.drawColor(mCurrentFlatColor);
@@ -311,9 +270,4 @@
         mPaint.setColor(mShelfColor);
         canvas.drawRoundRect(0, mShelfTop, width, height + mRadius, mRadius, mRadius, mPaint);
     }
-
-    @Override
-    public float getVisualTop() {
-        return mShelfTop;
-    }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
similarity index 98%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
rename to quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 9b2048e..656d59e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -133,11 +133,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
@@ -282,7 +277,6 @@
         mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
                 setVisibility(VISIBLE);
             }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
similarity index 81%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index a8d6442..dfb3860 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -52,7 +52,6 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
-import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
 import com.android.systemui.plugins.OverviewScreenshotActions;
@@ -85,7 +84,7 @@
             };
 
     private final BaseActivity mActivity;
-    private final TaskOverlay mOverlay;
+    private TaskOverlay mOverlay;
     private final boolean mIsDarkTextTheme;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -118,14 +117,13 @@
 
     public TaskThumbnailView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mOverlay = TaskOverlayFactory.INSTANCE.get(context).createOverlay(this);
         mPaint.setFilterBitmap(true);
         mBackgroundPaint.setColor(Color.WHITE);
         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mDimmingPaintAfterClearing.setColor(Color.BLACK);
         mActivity = BaseActivity.fromContext(context);
         mIsDarkTextTheme = Themes.getAttrBoolean(mActivity, R.attr.isWorkspaceDarkText);
-        // Initialize with dummy value. It is overridden later by TaskView
+        // Initialize with placeholder value. It is overridden later by TaskView
         mFullscreenParams = TEMP_PARAMS.get(context);
     }
 
@@ -134,7 +132,7 @@
      * @param task
      */
     public void bind(Task task) {
-        mOverlay.reset();
+        getTaskOverlay().reset();
         mTask = task;
         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
         mPaint.setColor(color);
@@ -176,7 +174,7 @@
             mBitmapShader = null;
             mThumbnailData = null;
             mPaint.setShader(null);
-            mOverlay.reset();
+            getTaskOverlay().reset();
         }
         if (mOverviewScreenshotActionsPlugin != null) {
             mOverviewScreenshotActionsPlugin.setupActions(getTaskView(), getThumbnail(), mActivity);
@@ -200,6 +198,9 @@
     }
 
     public TaskOverlay getTaskOverlay() {
+        if (mOverlay == null) {
+            mOverlay = getTaskView().getRecentsView().getTaskOverlayFactory().createOverlay(this);
+        }
         return mOverlay;
     }
 
@@ -326,22 +327,14 @@
         // Draw the background in all cases, except when the thumbnail data is opaque
         final boolean drawBackgroundOnly = mTask == null || mTask.isLocked || mBitmapShader == null
                 || mThumbnailData == null;
-        if (drawBackgroundOnly || mPreviewPositionHelper.mClipBottom > 0
-                || mThumbnailData.isTranslucent) {
+        if (drawBackgroundOnly || mThumbnailData.isTranslucent) {
             canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mBackgroundPaint);
             if (drawBackgroundOnly) {
                 return;
             }
         }
 
-        if (mPreviewPositionHelper.mClipBottom > 0) {
-            canvas.save();
-            canvas.clipRect(x, y, width, mPreviewPositionHelper.mClipBottom);
-            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
-            canvas.restore();
-        } else {
-            canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
-        }
+        canvas.drawRoundRect(x, y, width, height, cornerRadius, cornerRadius, mPaint);
     }
 
     public TaskView getTaskView() {
@@ -357,10 +350,10 @@
 
     private void updateOverlay() {
         if (mOverlayEnabled) {
-            mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
+            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.mMatrix,
                     mPreviewPositionHelper.mIsOrientationChanged);
         } else {
-            mOverlay.reset();
+            getTaskOverlay().reset();
         }
     }
 
@@ -379,7 +372,6 @@
     }
 
     private void updateThumbnailMatrix() {
-        mPreviewPositionHelper.mClipBottom = -1;
         mPreviewPositionHelper.mIsOrientationChanged = false;
         if (mBitmapShader != null && mThumbnailData != null) {
             mPreviewRect.set(0, 0, mThumbnailData.thumbnail.getWidth(),
@@ -473,40 +465,97 @@
         /**
          * Updates the matrix based on the provided parameters
          */
-        public void updateThumbnailMatrix(Rect thumbnailPosition, ThumbnailData thumbnailData,
+        public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
                 int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
             boolean isRotated = false;
             boolean isOrientationDifferent;
-            mClipBottom = -1;
 
-            float scale = thumbnailData.scale;
-            Rect activityInsets = dp.getInsets();
-            Rect thumbnailInsets = getBoundedInsets(activityInsets, thumbnailData.insets);
-            final float thumbnailWidth = thumbnailPosition.width()
-                    - (thumbnailInsets.left + thumbnailInsets.right) * scale;
-            final float thumbnailHeight = thumbnailPosition.height()
-                    - (thumbnailInsets.top + thumbnailInsets.bottom) * scale;
-
-            final float thumbnailScale;
             int thumbnailRotation = thumbnailData.rotation;
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
+            RectF thumbnailClipHint = new RectF(thumbnailData.insets);
+
+            float scale = thumbnailData.scale;
+            final float thumbnailScale;
 
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !dp.isMultiWindowMode
                     && thumbnailData.windowingMode == WINDOWING_MODE_FULLSCREEN;
             isOrientationDifferent = isOrientationChange(deltaRotate)
                     && windowingModeSupportsRotation;
-            if (canvasWidth == 0) {
+            if (canvasWidth == 0 || canvasHeight == 0 || scale == 0) {
                 // If we haven't measured , skip the thumbnail drawing and only draw the background
                 // color
                 thumbnailScale = 0f;
             } else {
                 // Rotate the screenshot if not in multi-window mode
                 isRotated = deltaRotate > 0 && windowingModeSupportsRotation;
-                // Scale the screenshot to always fit the width of the card.
-                thumbnailScale = isOrientationDifferent
-                        ? canvasWidth / thumbnailHeight
-                        : canvasWidth / thumbnailWidth;
+
+                float surfaceWidth = thumbnailBounds.width() / scale;
+                float surfaceHeight = thumbnailBounds.height() / scale;
+                float availableWidth = surfaceWidth
+                        - (thumbnailClipHint.left + thumbnailClipHint.right);
+                float availableHeight = surfaceHeight
+                        - (thumbnailClipHint.top + thumbnailClipHint.bottom);
+
+                final float targetW, targetH;
+                if (isOrientationDifferent) {
+                    targetW = canvasHeight;
+                    targetH = canvasWidth;
+                } else {
+                    targetW = canvasWidth;
+                    targetH = canvasHeight;
+                }
+                float canvasAspect = targetW / targetH;
+
+                // Update the clipHint such that
+                //   > the final clipped position has same aspect ratio as requested by canvas
+                //   > the clipped region is within the task insets if possible
+                //   > the clipped region is not scaled up when drawing. If that is not possible
+                //     while staying within the taskInsets, move outside the insets.
+                float croppedWidth = availableWidth;
+                if (croppedWidth < targetW) {
+                    croppedWidth = Math.min(targetW, surfaceWidth);
+                }
+
+                float croppedHeight = croppedWidth / canvasAspect;
+                if (croppedHeight > availableHeight) {
+                    croppedHeight = availableHeight;
+                    if (croppedHeight < targetH) {
+                        croppedHeight = Math.min(targetH, surfaceHeight);
+                    }
+                    croppedWidth = croppedHeight * canvasAspect;
+
+                    // One last check in case the task aspect radio messed up something
+                    if (croppedWidth > surfaceWidth) {
+                        croppedWidth = surfaceWidth;
+                        croppedHeight = croppedWidth / canvasAspect;
+                    }
+                }
+
+                // Update the clip hints
+                float halfExtraW = (availableWidth - croppedWidth) / 2;
+                thumbnailClipHint.left += halfExtraW;
+                thumbnailClipHint.right += halfExtraW;
+                if (thumbnailClipHint.left < 0) {
+                    thumbnailClipHint.right += thumbnailClipHint.left;
+                    thumbnailClipHint.left = 0;
+                } else if (thumbnailClipHint.right < 0) {
+                    thumbnailClipHint.left += thumbnailClipHint.right;
+                    thumbnailClipHint.right = 0;
+                }
+
+                float halfExtraH = (availableHeight - croppedHeight) / 2;
+                thumbnailClipHint.top += halfExtraH;
+                thumbnailClipHint.bottom += halfExtraH;
+                if (thumbnailClipHint.top < 0) {
+                    thumbnailClipHint.bottom += thumbnailClipHint.top;
+                    thumbnailClipHint.top = 0;
+                } else if (thumbnailClipHint.bottom < 0) {
+                    thumbnailClipHint.top += thumbnailClipHint.bottom;
+                    thumbnailClipHint.bottom = 0;
+                }
+
+                thumbnailScale = targetW / (croppedWidth * scale);
             }
 
             Rect splitScreenInsets = dp.getInsets();
@@ -516,24 +565,24 @@
                     mClippedInsets.offsetTo(splitScreenInsets.left * scale,
                             splitScreenInsets.top * scale);
                 } else {
-                    mClippedInsets.offsetTo(thumbnailInsets.left * scale,
-                            thumbnailInsets.top * scale);
+                    mClippedInsets.offsetTo(thumbnailClipHint.left * scale,
+                            thumbnailClipHint.top * scale);
                 }
                 mMatrix.setTranslate(
-                        -thumbnailInsets.left * scale,
-                        -thumbnailInsets.top * scale);
+                        -thumbnailClipHint.left * scale,
+                        -thumbnailClipHint.top * scale);
             } else {
-                setThumbnailRotation(deltaRotate, thumbnailInsets, scale, thumbnailPosition);
+                setThumbnailRotation(deltaRotate, thumbnailClipHint, scale, thumbnailBounds);
             }
 
             final float widthWithInsets;
             final float heightWithInsets;
             if (isOrientationDifferent) {
-                widthWithInsets = thumbnailPosition.height() * thumbnailScale;
-                heightWithInsets = thumbnailPosition.width() * thumbnailScale;
+                widthWithInsets = thumbnailBounds.height() * thumbnailScale;
+                heightWithInsets = thumbnailBounds.width() * thumbnailScale;
             } else {
-                widthWithInsets = thumbnailPosition.width() * thumbnailScale;
-                heightWithInsets = thumbnailPosition.height() * thumbnailScale;
+                widthWithInsets = thumbnailBounds.width() * thumbnailScale;
+                heightWithInsets = thumbnailBounds.height() * thumbnailScale;
             }
             mClippedInsets.left *= thumbnailScale;
             mClippedInsets.top *= thumbnailScale;
@@ -549,22 +598,9 @@
             }
 
             mMatrix.postScale(thumbnailScale, thumbnailScale);
-
-            float bitmapHeight = Math.max(0,
-                    (isOrientationDifferent ? thumbnailWidth : thumbnailHeight) * thumbnailScale);
-            if (Math.round(bitmapHeight) < canvasHeight) {
-                mClipBottom = bitmapHeight;
-            }
             mIsOrientationChanged = isOrientationDifferent;
         }
 
-        private Rect getBoundedInsets(Rect activityInsets, Rect insets) {
-            return new Rect(Math.min(insets.left, activityInsets.left),
-                    Math.min(insets.top, activityInsets.top),
-                    Math.min(insets.right, activityInsets.right),
-                    Math.min(insets.bottom, activityInsets.bottom));
-        }
-
         private int getRotationDelta(int oldRotation, int newRotation) {
             int delta = newRotation - oldRotation;
             if (delta < 0) delta += 4;
@@ -580,12 +616,12 @@
             return deltaRotation == Surface.ROTATION_90 || deltaRotation == Surface.ROTATION_270;
         }
 
-        private void setThumbnailRotation(int deltaRotate, Rect thumbnailInsets, float scale,
+        private void setThumbnailRotation(int deltaRotate, RectF thumbnailInsets, float scale,
                 Rect thumbnailPosition) {
-            int newLeftInset = 0;
-            int newTopInset = 0;
-            int translateX = 0;
-            int translateY = 0;
+            float newLeftInset = 0;
+            float newTopInset = 0;
+            float translateX = 0;
+            float translateY = 0;
 
             mMatrix.setRotate(90 * deltaRotate);
             switch (deltaRotate) { /* Counter-clockwise */
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
similarity index 90%
rename from quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
rename to quickstep/src/com/android/quickstep/views/TaskView.java
index 82fabac..6a2ab5a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -22,10 +22,14 @@
 import static android.view.Gravity.END;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
 
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.comp;
+import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 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;
@@ -52,7 +56,9 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -77,6 +83,7 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -121,6 +128,14 @@
 
     public static final long SCALE_ICON_DURATION = 120;
     private static final long DIM_ANIM_DURATION = 700;
+    /**
+     * This technically can be a vanilla {@link TouchDelegate} class, however that class requires
+     * setting the touch bounds at construction, so we'd repeatedly be created many instances
+     * unnecessarily as scrolling occurs, whereas {@link TransformingTouchDelegate} allows touch
+     * delegated bounds only to be updated.
+     */
+    private TransformingTouchDelegate mIconTouchDelegate;
+    private TransformingTouchDelegate mChipTouchDelegate;
 
     private static final List<Rect> SYSTEM_GESTURE_EXCLUSION_RECT =
             Collections.singletonList(new Rect());
@@ -185,6 +200,8 @@
     private int mStackHeight;
     private View mContextualChipWrapper;
     private View mContextualChip;
+    private final float[] mIconCenterCoords = new float[2];
+    private final float[] mChipCenterCoords = new float[2];
 
     public TaskView(Context context) {
         this(context, null);
@@ -210,10 +227,6 @@
             } else {
                 launchTask(true /* animate */);
             }
-
-            mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                    Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
-                    TaskUtils.getLaunchComponentKeyForTask(getTask().key));
             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                     .log(LAUNCHER_TASK_LAUNCH_TAP);
         });
@@ -230,14 +243,14 @@
      */
     public WorkspaceItemInfo getItemInfo() {
         ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
-        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
-        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
-        dummyInfo.user = componentKey.user;
-        dummyInfo.intent = new Intent().setComponent(componentKey.componentName);
-        dummyInfo.title = TaskUtils.getTitle(getContext(), getTask());
-        dummyInfo.screenId = getRecentsView().indexOfChild(this);
-        return dummyInfo;
+        WorkspaceItemInfo stubInfo = new WorkspaceItemInfo();
+        stubInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK;
+        stubInfo.container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER;
+        stubInfo.user = componentKey.user;
+        stubInfo.intent = new Intent().setComponent(componentKey.componentName);
+        stubInfo.title = TaskUtils.getTitle(getContext(), getTask());
+        stubInfo.screenId = getRecentsView().indexOfChild(this);
+        return stubInfo;
     }
 
     @Override
@@ -245,6 +258,54 @@
         super.onFinishInflate();
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
+        mIconTouchDelegate = new TransformingTouchDelegate(mIconView);
+    }
+
+    /**
+     * Whether the taskview should take the touch event from parent. Events passed to children
+     * that might require special handling.
+     */
+    public boolean offerTouchToChildren(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            computeAndSetIconTouchDelegate();
+            computeAndSetChipTouchDelegate();
+        }
+        if (mIconTouchDelegate != null && mIconTouchDelegate.onTouchEvent(event)) {
+            return true;
+        }
+        if (mChipTouchDelegate != null && mChipTouchDelegate.onTouchEvent(event)) {
+            return true;
+        }
+        return false;
+    }
+
+    private void computeAndSetIconTouchDelegate() {
+        float iconHalfSize = mIconView.getWidth() / 2f;
+        mIconCenterCoords[0] = mIconCenterCoords[1] = iconHalfSize;
+        getDescendantCoordRelativeToAncestor(mIconView, mActivity.getDragLayer(), mIconCenterCoords,
+                false);
+        mIconTouchDelegate.setBounds(
+                (int) (mIconCenterCoords[0] - iconHalfSize),
+                (int) (mIconCenterCoords[1] - iconHalfSize),
+                (int) (mIconCenterCoords[0] + iconHalfSize),
+                (int) (mIconCenterCoords[1] + iconHalfSize));
+    }
+
+    private void computeAndSetChipTouchDelegate() {
+        if (mContextualChipWrapper != null) {
+            float chipHalfWidth = mContextualChipWrapper.getWidth() / 2f;
+            float chipHalfHeight = mContextualChipWrapper.getHeight() / 2f;
+            mChipCenterCoords[0] = chipHalfWidth;
+            mChipCenterCoords[1] = chipHalfHeight;
+            getDescendantCoordRelativeToAncestor(mContextualChipWrapper, mActivity.getDragLayer(),
+                    mChipCenterCoords,
+                    false);
+            mChipTouchDelegate.setBounds(
+                    (int) (mChipCenterCoords[0] - chipHalfWidth),
+                    (int) (mChipCenterCoords[1] - chipHalfHeight),
+                    (int) (mChipCenterCoords[0] + chipHalfWidth),
+                    (int) (mChipCenterCoords[1] + chipHalfHeight));
+        }
     }
 
     /**
@@ -254,6 +315,9 @@
      * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
      */
     public void setModalness(float modalness) {
+        if (mModalness == modalness) {
+            return;
+        }
         mModalness = modalness;
         mIconView.setAlpha(comp(modalness));
         if (mContextualChip != null) {
@@ -263,7 +327,6 @@
         if (mContextualChipWrapper != null) {
             mContextualChipWrapper.setAlpha(comp(modalness));
         }
-
         updateFooterVerticalOffset(mFooterVerticalOffset);
     }
 
@@ -331,19 +394,11 @@
     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            RecentsView recentsView = getRecentsView();
             if (isRunningTask()) {
-                recentsView.finishRecentsAnimation(false /* toRecents */,
+                getRecentsView().finishRecentsAnimation(false /* toRecents */,
                         () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
             } else {
-                // This is a workaround against the WM issue that app open is not correctly animated
-                // when recents animation is being cleaned up (b/143774568). When that's possible,
-                // we should rely on the framework side to cancel the recents animation, and we will
-                // clean up the screenshot on the launcher side while we launch the next task.
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                () -> launchTaskInternal(animate, freezeTaskList, resultCallback,
-                                        resultCallbackHandler)));
+                launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
             }
         } else {
             launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
@@ -466,18 +521,18 @@
         int thumbnailPadding = (int) getResources().getDimension(R.dimen.task_thumbnail_top_margin);
         LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
         switch (orientationHandler.getRotation()) {
-            case Surface.ROTATION_90:
+            case ROTATION_90:
                 iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL;
                 iconParams.rightMargin = -thumbnailPadding;
                 iconParams.leftMargin = 0;
                 iconParams.topMargin = snapshotParams.topMargin / 2;
                 break;
-            case Surface.ROTATION_180:
+            case ROTATION_180:
                 iconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
                 iconParams.bottomMargin = -thumbnailPadding;
                 iconParams.leftMargin = iconParams.topMargin = iconParams.rightMargin = 0;
                 break;
-            case Surface.ROTATION_270:
+            case ROTATION_270:
                 iconParams.gravity = (isRtl ? END : START) | CENTER_VERTICAL;
                 iconParams.leftMargin = -thumbnailPadding;
                 iconParams.rightMargin = 0;
@@ -510,7 +565,11 @@
                 .getInterpolation(progress);
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
-
+        if (mContextualChip != null && mContextualChipWrapper != null) {
+            mContextualChipWrapper.setAlpha(scale);
+            mContextualChip.setScaleX(scale);
+            mContextualChip.setScaleY(scale);
+        }
         updateFooterVerticalOffset(1.0f - scale);
     }
 
@@ -689,6 +748,7 @@
                 mContextualChip.animate().scaleX(1f).scaleY(1f).setDuration(50);
             }
             if (mContextualChipWrapper != null) {
+                mChipTouchDelegate = new TransformingTouchDelegate(mContextualChipWrapper);
                 mContextualChipWrapper.animate().alpha(1f).setDuration(50);
             }
         }
@@ -710,6 +770,7 @@
         View oldContextualChipWrapper = mContextualChipWrapper;
         mContextualChipWrapper = null;
         mContextualChip = null;
+        mChipTouchDelegate = null;
         return oldContextualChipWrapper;
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java b/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
deleted file mode 100644
index 5904fcd..0000000
--- a/quickstep/tests/src/com/android/quickstep/AppPredictionsUITests.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.quickstep;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.app.prediction.AppPredictor;
-import android.app.prediction.AppTarget;
-import android.app.prediction.AppTargetId;
-import android.content.ComponentName;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.os.Process;
-import android.view.View;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.appprediction.PredictionRowView;
-import com.android.launcher3.appprediction.PredictionUiStateManager;
-import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
-import com.android.launcher3.model.AppLaunchTracker;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class AppPredictionsUITests extends AbstractQuickStepTest {
-
-    private LauncherActivityInfo mSampleApp1;
-    private LauncherActivityInfo mSampleApp2;
-    private LauncherActivityInfo mSampleApp3;
-
-    private AppPredictor.Callback mCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        List<LauncherActivityInfo> activities = mTargetContext.getSystemService(LauncherApps.class)
-                .getActivityList(null, Process.myUserHandle());
-        mSampleApp1 = activities.get(0);
-        mSampleApp2 = activities.get(1);
-        mSampleApp3 = activities.get(2);
-
-        // Disable app tracker
-        AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
-        PredictionUiStateManager.INSTANCE.initializeForTesting(null);
-
-        mCallback = PredictionUiStateManager.INSTANCE.get(mTargetContext).appPredictorCallback(
-                Client.HOME);
-
-        mDevice.setOrientationNatural();
-    }
-
-    @After
-    public void tearDown() throws Throwable {
-        AppLaunchTracker.INSTANCE.initializeForTesting(null);
-        PredictionUiStateManager.INSTANCE.initializeForTesting(null);
-        mDevice.unfreezeRotation();
-    }
-
-    /**
-     * Test that prediction UI is updated as soon as we get predictions from the system
-     */
-    @Test
-    public void testPredictionExistsInAllApps() {
-        mLauncher.pressHome().switchToAllApps();
-
-        // Dispatch an update
-        sendPredictionUpdate(mSampleApp1, mSampleApp2);
-        // The first update should apply immediately.
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> getPredictedApp(launcher).size() == 2);
-    }
-
-    /**
-     * Test that prediction update is deferred if it is already visible
-     */
-    @Test
-    public void testPredictionsDeferredUntilHome() {
-        mDevice.pressHome();
-        sendPredictionUpdate(mSampleApp1, mSampleApp2);
-        mLauncher.pressHome().switchToAllApps();
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> getPredictedApp(launcher).size() == 2);
-
-        // Update predictions while all-apps is visible
-        sendPredictionUpdate(mSampleApp1, mSampleApp2, mSampleApp3);
-        assertEquals(2, getFromLauncher(this::getPredictedApp).size());
-
-        // Go home and go back to all-apps
-        mLauncher.pressHome().switchToAllApps();
-        assertEquals(3, getFromLauncher(this::getPredictedApp).size());
-    }
-
-    @Test
-    public void testPredictionsDisabled() {
-        mDevice.pressHome();
-        sendPredictionUpdate();
-        mLauncher.pressHome().switchToAllApps();
-
-        waitForLauncherCondition("Predictions were not updated in loading state",
-                launcher -> launcher.getAppsView().getFloatingHeaderView()
-                        .findFixedRowByType(PredictionRowView.class).getVisibility() == View.GONE);
-        assertFalse(PredictionUiStateManager.INSTANCE.get(mTargetContext)
-                .getCurrentState().isEnabled);
-    }
-
-    public ArrayList<BubbleTextView> getPredictedApp(Launcher launcher) {
-        PredictionRowView container = launcher.getAppsView().getFloatingHeaderView()
-                .findFixedRowByType(PredictionRowView.class);
-
-        ArrayList<BubbleTextView> predictedAppViews = new ArrayList<>();
-        for (int i = 0; i < container.getChildCount(); i++) {
-            View view = container.getChildAt(i);
-            if (view instanceof BubbleTextView && view.getVisibility() == View.VISIBLE) {
-                predictedAppViews.add((BubbleTextView) view);
-            }
-        }
-        return predictedAppViews;
-    }
-
-    private void sendPredictionUpdate(LauncherActivityInfo... activities) {
-        getOnUiThread(() -> {
-            List<AppTarget> targets = new ArrayList<>(activities.length);
-            for (LauncherActivityInfo info : activities) {
-                ComponentName cn = info.getComponentName();
-                AppTarget target = new AppTarget.Builder(
-                        new AppTargetId("app:" + cn), cn.getPackageName(), info.getUser())
-                        .setClassName(cn.getClassName())
-                        .build();
-                targets.add(target);
-            }
-            mCallback.onTargetsAvailable(targets);
-            return null;
-        });
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index bf093fd..ecd4e2b 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.tapl.Background;
 import com.android.launcher3.tapl.LauncherInstrumentation.NavigationModel;
 import com.android.launcher3.tapl.Overview;
+import com.android.launcher3.tapl.OverviewActions;
 import com.android.launcher3.tapl.OverviewTask;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.TaplTestsLauncher3;
@@ -68,11 +69,14 @@
         });
     }
 
-    private void startTestApps() throws Exception {
+    public static void startTestApps() throws Exception {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
         startTestActivity(2);
+    }
 
+    private void startTestAppsWithCheck() throws Exception {
+        startTestApps();
         executeOnLauncher(launcher -> assertTrue(
                 "Launcher activity is the top activity; expecting another activity to be the top "
                         + "one",
@@ -105,7 +109,7 @@
     @Test
     @PortraitLandscape
     public void testOverview() throws Exception {
-        startTestApps();
+        startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.pressHome().switchToOverview();
         assertTrue("Launcher internal state didn't switch to Overview",
@@ -189,6 +193,22 @@
                         0, getTaskCount(launcher)));
     }
 
+    /**
+     * Smoke test for action buttons: Presses all the buttons and makes sure no crashes occur.
+     */
+    @Test
+    @NavigationModeSwitch
+    @PortraitLandscape
+    public void testOverviewActions() throws Exception {
+        if (mLauncher.getNavigationModel() != NavigationModel.TWO_BUTTON) {
+            startTestAppsWithCheck();
+            OverviewActions actionsView =
+                    mLauncher.pressHome().switchToOverview().getOverviewActions();
+            actionsView.clickAndDismissScreenshot();
+            actionsView.clickAndDismissShare();
+        }
+    }
+
     private int getCurrentOverviewPage(Launcher launcher) {
         return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
     }
diff --git a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
index 115294a..72116eb 100644
--- a/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
+++ b/quickstep/tests/src/com/android/quickstep/ViewInflationDuringSwipeUp.java
@@ -71,7 +71,7 @@
 
 /**
  * Test to verify view inflation does not happen during swipe up.
- * To verify view inflation, we setup a dummy ViewConfiguration and check if any call to that class
+ * To verify view inflation, we setup a stub ViewConfiguration and check if any call to that class
  * does from a View.init method or not.
  *
  * Alternative approaches considered:
@@ -138,13 +138,13 @@
     @Test
     @NavigationModeSwitch(mode = ZERO_BUTTON)
     public void testSwipeUpFromApp_widget_update() {
-        String dummyText = "Some random dummy text";
+        String stubText = "Some random stub text";
 
         executeSwipeUpTestWithWidget(
                 widgetId -> { },
                 widgetId -> AppWidgetManager.getInstance(getContext())
-                        .updateAppWidget(widgetId, createMainWidgetViews(dummyText)),
-                dummyText);
+                        .updateAppWidget(widgetId, createMainWidgetViews(stubText)),
+                stubText);
     }
 
     @Test
diff --git a/res/animator-v23/discovery_bounce.xml b/res/animator-v23/discovery_bounce.xml
deleted file mode 100644
index f554853..0000000
--- a/res/animator-v23/discovery_bounce.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-** Copyright 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.
-*/
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
-    android:duration="2166"
-    android:repeatCount="5">
-    <propertyValuesHolder
-        android:propertyName="progress"
-        android:valueType="floatType">
-        <keyframe
-            android:fraction="0"
-            android:value="1f" />
-        <keyframe
-            android:fraction="0.246"
-            android:value="1f" />
-        <keyframe
-            android:fraction=".423"
-            android:interpolator="@interpolator/disco_bounce"
-            android:value="0.9738f" />
-        <keyframe
-            android:fraction="0.754"
-            android:interpolator="@interpolator/disco_bounce"
-            android:value="1f" />
-        <keyframe
-            android:fraction="1"
-            android:value="1f" />
-    </propertyValuesHolder>
-</objectAnimator>
diff --git a/res/animator/discovery_bounce.xml b/res/animator/discovery_bounce.xml
index f02ebdb..f554853 100644
--- a/res/animator/discovery_bounce.xml
+++ b/res/animator/discovery_bounce.xml
@@ -16,20 +16,28 @@
 ** limitations under the License.
 */
 -->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
-    android:ordering="sequentially">
-    <objectAnimator
-        android:duration="166"
-        android:interpolator="@interpolator/disco_bounce"
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:duration="2166"
+    android:repeatCount="5">
+    <propertyValuesHolder
         android:propertyName="progress"
-        android:startOffset="750"
-        android:valueFrom="1f"
-        android:valueTo="0.9438f"
-        android:valueType="floatType" />
-    <objectAnimator
-        android:duration="500"
-        android:interpolator="@interpolator/disco_bounce"
-        android:propertyName="progress"
-        android:valueTo="1f"
-        android:valueType="floatType" />
-</set>
+        android:valueType="floatType">
+        <keyframe
+            android:fraction="0"
+            android:value="1f" />
+        <keyframe
+            android:fraction="0.246"
+            android:value="1f" />
+        <keyframe
+            android:fraction=".423"
+            android:interpolator="@interpolator/disco_bounce"
+            android:value="0.9738f" />
+        <keyframe
+            android:fraction="0.754"
+            android:interpolator="@interpolator/disco_bounce"
+            android:value="1f" />
+        <keyframe
+            android:fraction="1"
+            android:value="1f" />
+    </propertyValuesHolder>
+</objectAnimator>
diff --git a/res/color-v24/all_apps_bg_hand_fill.xml b/res/color/all_apps_bg_hand_fill.xml
similarity index 100%
rename from res/color-v24/all_apps_bg_hand_fill.xml
rename to res/color/all_apps_bg_hand_fill.xml
diff --git a/res/color-v24/all_apps_bg_hand_fill_dark.xml b/res/color/all_apps_bg_hand_fill_dark.xml
similarity index 100%
rename from res/color-v24/all_apps_bg_hand_fill_dark.xml
rename to res/color/all_apps_bg_hand_fill_dark.xml
diff --git a/res/drawable-v24/drag_handle_indicator_shadow.xml b/res/drawable-v24/drag_handle_indicator_shadow.xml
deleted file mode 100644
index 774bc38..0000000
--- a/res/drawable-v24/drag_handle_indicator_shadow.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.graphics.ShadowDrawable
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@drawable/drag_handle_indicator_no_shadow"
-    android:elevation="@dimen/vertical_drag_handle_elevation" />
diff --git a/res/drawable-v26/ic_deepshortcut_placeholder.xml b/res/drawable-v26/ic_deepshortcut_placeholder.xml
deleted file mode 100644
index 3fa8506..0000000
--- a/res/drawable-v26/ic_deepshortcut_placeholder.xml
+++ /dev/null
@@ -1,20 +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.
--->
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="?attr/popupColorSecondary"/>
-    <foreground android:drawable="?attr/popupColorSecondary"/>
-</adaptive-icon>
diff --git a/res/drawable-v26/ic_launcher_home.xml b/res/drawable-v26/ic_launcher_home.xml
deleted file mode 100644
index 7038775..0000000
--- a/res/drawable-v26/ic_launcher_home.xml
+++ /dev/null
@@ -1,21 +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.
--->
-<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
-    <background android:drawable="@color/icon_background" />
-    <foreground>
-        <bitmap android:src="@mipmap/ic_launcher_home_foreground"/>
-    </foreground>
-</adaptive-icon>
diff --git a/res/drawable/drag_handle_indicator_no_shadow.xml b/res/drawable/drag_handle_indicator_no_shadow.xml
deleted file mode 100644
index 341e60c..0000000
--- a/res/drawable/drag_handle_indicator_no_shadow.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="@dimen/vertical_drag_handle_width"
-    android:height="@dimen/vertical_drag_handle_height"
-    android:viewportWidth="18.0"
-    android:viewportHeight="6.0"
-    android:tint="?attr/workspaceTextColor" >
-
-    <path
-        android:pathData="M17,6c-0.15,0-0.3-0.03-0.45-0.11L9,2.12L1.45,5.89c-0.5,0.25-1.09,
-        0.05-1.34-0.45S0.06,4.35,0.55,4.11l8-4c0.28-0.14,0.61-0.14,0.89,0l8,4c0.49,0.25,0.69,
-        0.85,0.45,1.34C17.72,5.8,17.37,6,17,6z"
-        android:fillColor="@android:color/white" />
-</vector>
diff --git a/res/drawable-v24/ic_block_shadow.xml b/res/drawable/ic_block_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_block_shadow.xml
rename to res/drawable/ic_block_shadow.xml
diff --git a/res/drawable/ic_deepshortcut_placeholder.xml b/res/drawable/ic_deepshortcut_placeholder.xml
index 85a9694..3fa8506 100644
--- a/res/drawable/ic_deepshortcut_placeholder.xml
+++ b/res/drawable/ic_deepshortcut_placeholder.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
+<!--
+     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.
@@ -13,10 +14,7 @@
      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="oval">
-    <solid android:color="?attr/popupColorSecondary" />
-    <size
-        android:height="32dp"
-        android:width="32dp" />
-</shape>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="?attr/popupColorSecondary"/>
+    <foreground android:drawable="?attr/popupColorSecondary"/>
+</adaptive-icon>
diff --git a/res/drawable/ic_launcher_home.xml b/res/drawable/ic_launcher_home.xml
index a6f2519..7038775 100644
--- a/res/drawable/ic_launcher_home.xml
+++ b/res/drawable/ic_launcher_home.xml
@@ -13,6 +13,9 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<bitmap
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:src="@mipmap/ic_launcher_home" />
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/icon_background" />
+    <foreground>
+        <bitmap android:src="@mipmap/ic_launcher_home_foreground"/>
+    </foreground>
+</adaptive-icon>
diff --git a/res/drawable-v24/ic_remove_shadow.xml b/res/drawable/ic_remove_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_remove_shadow.xml
rename to res/drawable/ic_remove_shadow.xml
diff --git a/res/drawable-v24/ic_setup_shadow.xml b/res/drawable/ic_setup_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_setup_shadow.xml
rename to res/drawable/ic_setup_shadow.xml
diff --git a/res/drawable-v24/ic_uninstall_shadow.xml b/res/drawable/ic_uninstall_shadow.xml
similarity index 100%
rename from res/drawable-v24/ic_uninstall_shadow.xml
rename to res/drawable/ic_uninstall_shadow.xml
diff --git a/res/layout/home_settings.xml b/res/layout/home_settings.xml
new file mode 100644
index 0000000..0f2461a
--- /dev/null
+++ b/res/layout/home_settings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <EditText
+        android:id="@+id/filter_box"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/developer_options_filter_margins"
+        android:hint="@string/developer_options_filter_hint"
+        android:visibility="gone"
+        />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:id="@android:id/list_container"/>
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index a137908..0c18c8a 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -28,6 +28,12 @@
         android:clipToPadding="false"
         android:importantForAccessibility="no">
 
+        <com.android.launcher3.views.AccessibilityActionsView
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:contentDescription="@string/home_screen"
+            />
+
         <!-- The workspace contains 5 screens of cells -->
         <!-- DO NOT CHANGE THE ID -->
         <com.android.launcher3.Workspace
diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml
new file mode 100644
index 0000000..bd0e42b
--- /dev/null
+++ b/res/layout/search_result_hero_app.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.HeroSearchResultView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:padding="@dimen/dynamic_grid_edge_margin">
+
+    <FrameLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1">
+        <com.android.launcher3.BubbleTextView
+            android:id="@+id/bubble_text"
+            style="@style/BaseIcon"
+            android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
+            android:gravity="start|center_vertical"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="16sp"
+            android:layout_height="wrap_content"
+            launcher:iconDisplay="hero_app"
+            launcher:layoutHorizontal="true"/>
+
+        <View
+            android:id="@+id/icon"
+            android:layout_width="@dimen/deep_shortcut_icon_size"
+            android:layout_height="@dimen/deep_shortcut_icon_size"
+            android:layout_gravity="start|center_vertical"
+            android:background="@drawable/ic_deepshortcut_placeholder"/>
+    </FrameLayout>
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_0"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        android:gravity="start|center_vertical"
+        android:textAlignment="center"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="12sp"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false"/>
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_1"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        android:gravity="start|center_vertical"
+        android:textAlignment="center"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="12sp"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false"/>
+
+</com.android.launcher3.views.HeroSearchResultView>
\ No newline at end of file
diff --git a/res/layout/search_result_people_item.xml b/res/layout/search_result_people_item.xml
new file mode 100644
index 0000000..a603941
--- /dev/null
+++ b/res/layout/search_result_people_item.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchResultPeopleView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:gravity="center_vertical"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:orientation="horizontal">
+
+    <View
+        android:id="@+id/icon"
+        android:layout_marginRight="8dp"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size" />
+
+    <TextView
+        android:layout_width="0dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:id="@+id/title"
+        android:layout_height="wrap_content"
+        android:layout_weight="1" />
+
+    <ImageButton
+        android:id="@+id/provider_0"
+        android:scaleType="fitCenter"
+        android:adjustViewBounds="true"
+        android:layout_margin="5dp"
+        android:background="?android:attr/selectableItemBackground"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size" />
+
+    <ImageButton
+        android:id="@+id/provider_1"
+        android:layout_margin="5dp"
+        android:scaleType="fitCenter"
+        android:adjustViewBounds="true"
+        android:background="?android:attr/selectableItemBackground"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size" />
+
+    <ImageButton
+        android:id="@+id/provider_2"
+        android:layout_margin="5dp"
+        android:scaleType="fitCenter"
+        android:adjustViewBounds="true"
+        android:background="?android:attr/selectableItemBackground"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size" />
+
+</com.android.launcher3.views.SearchResultPeopleView>
\ No newline at end of file
diff --git a/res/layout/search_result_play_item.xml b/res/layout/search_result_play_item.xml
new file mode 100644
index 0000000..4e82eaf
--- /dev/null
+++ b/res/layout/search_result_play_item.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:padding="8dp"
+    android:orientation="horizontal">
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size"
+        android:layout_gravity="start|center_vertical"
+        android:background="@drawable/ic_deepshortcut_placeholder" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:padding="8dp">
+
+        <TextView
+            android:id="@+id/title_view"
+            style="@style/TextHeadline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="16sp" />
+
+        <TextView
+            android:id="@+id/detail_0"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary" />
+
+        <TextView
+            android:id="@+id/detail_1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+
+        <TextView
+            android:id="@+id/detail_2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textColor="?android:attr/textColorPrimary"
+            android:visibility="gone" />
+    </LinearLayout>
+    <Button
+        android:id="@+id/try_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start|center_vertical"
+        android:background="?android:attr/selectableItemBackground"
+        android:text="@string/search_action_try_now">
+    </Button>
+
+
+</com.android.launcher3.views.SearchResultPlayItem>
diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml
new file mode 100644
index 0000000..19daf34
--- /dev/null
+++ b/res/layout/search_result_settings_row.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchSettingsRowView xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/TextHeadline"
+    android:id="@+id/section_title"
+    android:background="?android:attr/selectableItemBackground"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="center_vertical"
+    android:padding="4dp"
+    android:minHeight="48dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="14sp">
+
+    <TextView
+        android:id="@+id/title"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="4dp"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp" />
+
+    <TextView
+        android:id="@+id/description"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="14sp" />
+
+    <TextView
+        android:id="@+id/breadcrumbs"
+        style="@style/TextTitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:alpha=".7"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textSize="14sp" />
+
+
+</com.android.launcher3.views.SearchSettingsRowView>
\ No newline at end of file
diff --git a/res/layout/search_result_shortcut.xml b/res/layout/search_result_shortcut.xml
new file mode 100644
index 0000000..c350c97
--- /dev/null
+++ b/res/layout/search_result_shortcut.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchResultShortcut xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:padding="@dimen/dynamic_grid_edge_margin">
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/bubble_text"
+        style="@style/BaseIcon"
+        android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:layout_height="wrap_content"
+        launcher:iconDisplay="hero_app"
+        launcher:layoutHorizontal="true" />
+
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size"
+        android:layout_gravity="start|center_vertical"
+        android:background="@drawable/ic_deepshortcut_placeholder" />
+
+</com.android.launcher3.views.SearchResultShortcut>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/res/layout/search_result_slice.xml
similarity index 69%
rename from quickstep/recents_ui_overrides/res/values/config.xml
rename to res/layout/search_result_slice.xml
index 120e034..ea1d49a 100644
--- a/quickstep/recents_ui_overrides/res/values/config.xml
+++ b/res/layout/search_result_slice.xml
@@ -5,7 +5,7 @@
      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
+          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,
@@ -13,6 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<resources>
-    <integer name="max_depth_blur_radius">150</integer>
-</resources>
\ No newline at end of file
+<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="4dp" />
\ No newline at end of file
diff --git a/res/layout/search_section_title.xml b/res/layout/search_section_title.xml
new file mode 100644
index 0000000..9419015
--- /dev/null
+++ b/res/layout/search_section_title.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchSectionHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/section_title"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:fontFamily="@style/TextHeadline"
+    android:padding="4dp"
+    android:textColor="?android:attr/textColorPrimary"
+    android:textSize="14sp" />
\ No newline at end of file
diff --git a/res/mipmap-hdpi/ic_launcher_home.png b/res/mipmap-hdpi/ic_launcher_home.png
deleted file mode 100644
index d068d92..0000000
--- a/res/mipmap-hdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_home.png b/res/mipmap-mdpi/ic_launcher_home.png
deleted file mode 100644
index 16c8ec2..0000000
--- a/res/mipmap-mdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_home.png b/res/mipmap-xhdpi/ic_launcher_home.png
deleted file mode 100644
index 8b2671b..0000000
--- a/res/mipmap-xhdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_home.png b/res/mipmap-xxhdpi/ic_launcher_home.png
deleted file mode 100644
index 43d8b7d..0000000
--- a/res/mipmap-xxhdpi/ic_launcher_home.png
+++ /dev/null
Binary files differ
diff --git a/res/values-night-v26/styles.xml b/res/values-night/styles.xml
similarity index 100%
rename from res/values-night-v26/styles.xml
rename to res/values-night/styles.xml
diff --git a/res/values-v22/styles.xml b/res/values-v22/styles.xml
deleted file mode 100644
index f86db7a..0000000
--- a/res/values-v22/styles.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* 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.
-*/
--->
-
-<resources>
-
-    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
-        <item name="widgetsTheme">@style/WidgetContainerTheme</item>
-    </style>
-
-</resources>
\ No newline at end of file
diff --git a/res/values-v26/bools.xml b/res/values-v26/bools.xml
deleted file mode 100644
index ad8c7a1..0000000
--- a/res/values-v26/bools.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 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.
-*/
--->
-
-<resources>
-    <bool name="notification_dots_enabled">true</bool>
-
-    <bool name="enable_install_shortcut_api">false</bool>
-</resources>
\ No newline at end of file
diff --git a/res/values-v26/styles.xml b/res/values-v26/styles.xml
deleted file mode 100644
index d2f0802..0000000
--- a/res/values-v26/styles.xml
+++ /dev/null
@@ -1,31 +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.
-*/
--->
-<resources>
-    <!-- Theme for the widget container. -->
-    <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="android:colorPrimaryDark">#E8EAED</item>
-        <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-    </style>
-    <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
-        <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
-    </style>
-
-</resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 26e6cba..acb8221 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -57,6 +57,7 @@
             <enum name="folder" value="2" />
             <enum name="widget_section" value="3" />
             <enum name="shortcut_popup" value="4" />
+            <enum name="hero_app" value="5" />
         </attr>
         <attr name="centerVertically" format="boolean" />
     </declare-styleable>
diff --git a/res/values/bools.xml b/res/values/bools.xml
deleted file mode 100644
index bc2c678..0000000
--- a/res/values/bools.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/* Copyright 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.
-*/
--->
-
-<resources>
-    <bool name="notification_dots_enabled">false</bool>
-
-    <bool name="enable_install_shortcut_api">true</bool>
-</resources>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 043ad9a..f56fbaa 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -35,9 +35,6 @@
 
     <color name="icon_background">#E0E0E0</color> <!-- Gray 300 -->
 
-    <color name="all_apps_bg_hand_fill">#E5E5E5</color>
-    <color name="all_apps_bg_hand_fill_dark">#9AA0A6</color>
-
     <color name="gesture_tutorial_ripple_color">#A0C2F9</color> <!-- Light Blue -->
     <color name="gesture_tutorial_fake_task_view_color">#6DA1FF</color> <!-- Light Blue -->
     <color name="gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
diff --git a/res/values/config.xml b/res/values/config.xml
index 75fcc90..325b62f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -59,7 +59,6 @@
     <bool name="hotseat_transpose_layout_with_orientation">true</bool>
 
     <!-- Various classes overriden by projects/build flavors. -->
-    <string name="app_filter_class" translatable="false"></string>
     <string name="user_event_dispatcher_class" translatable="false"></string>
     <string name="folder_name_provider_class" translatable="false"></string>
     <string name="stats_log_manager_class" translatable="false"></string>
@@ -69,7 +68,7 @@
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
     <string name="launcher_activity_logic_class" translatable="false"></string>
-    <string name="prediction_model_class" translatable="false"></string>
+    <string name="model_delegate_class" translatable="false"></string>
 
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
@@ -187,4 +186,6 @@
 
     <string-array name="live_wallpapers_remove_sysui_scrims">
     </string-array>
+
+    <string-array name="filtered_components" ></string-array>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 947e635..f59f02f 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -40,14 +40,6 @@
     <dimen name="workspace_page_indicator_line_height">1dp</dimen>
     <dimen name="workspace_page_indicator_overlap_workspace">0dp</dimen>
 
-    <!-- Hotseat/all-apps scrim -->
-    <dimen name="all_apps_scrim_blur">4dp</dimen>
-    <dimen name="vertical_drag_handle_width">18dp</dimen>
-    <dimen name="vertical_drag_handle_height">6dp</dimen>
-    <dimen name="vertical_drag_handle_elevation">1dp</dimen>
-    <dimen name="vertical_drag_handle_touch_size">48dp</dimen>
-    <dimen name="vertical_drag_handle_padding_in_vertical_bar_layout">16dp</dimen>
-
 <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">48dp</dimen>
     <dimen name="drop_target_vertical_gap">20dp</dimen>
@@ -247,6 +239,9 @@
     <dimen name="snackbar_min_text_size">12sp</dimen>
     <dimen name="snackbar_max_text_size">14sp</dimen>
 
+<!-- Developer Options -->
+    <dimen name="developer_options_filter_margins">10dp</dimen>
+
 <!-- Theming related -->
     <dimen name="default_dialog_corner_radius">8dp</dimen>
 
diff --git a/res/values/drawables.xml b/res/values/drawables.xml
deleted file mode 100644
index 7d63142..0000000
--- a/res/values/drawables.xml
+++ /dev/null
@@ -1,22 +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.
--->
-<resources>
-    <drawable name="ic_setup_shadow">@drawable/ic_setting</drawable>
-    <drawable name="ic_remove_shadow">@drawable/ic_remove_no_shadow</drawable>
-    <drawable name="ic_uninstall_shadow">@drawable/ic_uninstall_no_shadow</drawable>
-    <drawable name="ic_block_shadow">@drawable/ic_block_no_shadow</drawable>
-    <drawable name="all_apps_arrow_shadow">@drawable/drag_handle_indicator_no_shadow</drawable>
-</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 935bb40..6ab8150 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,7 +36,7 @@
     <!-- Message shown when a shortcut is not available. It could have been temporarily disabled and may start working again after some time. -->
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
     <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
-    <string name="home_screen">Home screen</string>
+    <string name="home_screen">Home</string>
     <!-- Label for showing custom action list of a shortcut or widget. [CHAR_LIMIT=30] -->
     <string name="custom_actions">Custom actions</string>
 
@@ -58,6 +58,8 @@
     <!-- All Apps -->
     <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
     <string name="all_apps_search_bar_hint">Search apps</string>
+    <!-- Search bar text in the apps view. [CHAR_LIMIT=50] -->
+    <string name="all_apps_on_device_search_bar_hint">Search this phone and more...</string>
     <!-- Loading apps text. [CHAR_LIMIT=50] -->
     <string name="all_apps_loading_message">Loading apps&#8230;</string>
     <!-- No-search-results text. [CHAR_LIMIT=50] -->
@@ -67,6 +69,12 @@
     <!-- Label for an icon representing any generic app. [CHAR_LIMIT=50] -->
     <string name="label_application">App</string>
 
+    <!--All apps Search-->
+    <!-- Section title for apps [CHAR_LIMIT=50] -->
+    <string name="search_corpus_apps">Apps</string>
+    <!-- try instant app action for play search result [CHAR_LIMIT=50 -->
+    <string name="search_action_try_now">Try Now</string>
+
     <!-- Popup items -->
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
     <string name="notifications_header">Notifications</string>
@@ -88,10 +96,6 @@
     <string name="all_apps_button_personal_label">Personal apps list</string>
     <string name="all_apps_button_work_label">Work apps list</string>
 
-    <!-- Label for button in all applications label to go back home (to the workspace / desktop)
-         for accessibilty (spoken when the button gets focus). -->
-    <string name="all_apps_home_button_label">Home</string>
-
     <!-- Label for remove drop target (from the homescreen only).
          May appear next to uninstall_drop_target_label [CHAR_LIMIT=20] -->
     <string name="remove_drop_target_label">Remove</string>
@@ -348,7 +352,8 @@
     <!-- content description for paused work apps list -->
     <string name="work_apps_paused_content_description">Work profile is paused. Work apps can\’t send you notifications, use your battery, or access your location</string>
 
-
+    <!-- A hint shown in launcher settings develop options filter box -->
+    <string name="developer_options_filter_hint">Filter</string>
 
     <!-- A tip shown pointing at work toggle -->
     <string name="work_switch_tip">Pause work apps and notifications</string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 25f21f3..fd3d873 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -143,12 +143,21 @@
     <style name="AppTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark.DarkMainColor" />
     <style name="AppTheme.Dark.DarkText" parent="@style/LauncherTheme.Dark.DarkText" />
 
-    <style name="AppItemActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.Alert">
+    <style name="AppItemActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
         <item name="widgetsTheme">@style/WidgetContainerTheme</item>
     </style>
 
     <style name="HomeSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings">
         <item name="android:navigationBarColor">@android:color/transparent</item>
+        <item name="preferenceTheme">@style/HomeSettingsPreferenceTheme</item>
+    </style>
+
+    <style name="HomeSettingsPreferenceTheme" parent="@style/PreferenceThemeOverlay.v14.Material">
+        <item name="preferenceFragmentCompatStyle">@style/HomeSettingsFragmentCompatStyle</item>
+    </style>
+
+    <style name="HomeSettingsFragmentCompatStyle" parent="@style/PreferenceFragment.Material">
+        <item name="android:layout">@layout/home_settings</item>
     </style>
 
     <!--
@@ -162,14 +171,16 @@
         <item name="android:textColorSecondary">?attr/workspaceTextColor</item>
     </style>
 
-    <!-- Theme for the widget container. Overridden on API 26. -->
+    <!-- Theme for the widget container. -->
     <style name="WidgetContainerTheme" parent="@android:style/Theme.DeviceDefault.Settings">
-        <item name="android:colorEdgeEffect">?android:attr/textColorSecondaryInverse</item>
-        <item name="android:textColorPrimary">?android:attr/textColorPrimaryInverse</item>
-        <item name="android:textColorSecondary">?android:attr/textColorSecondaryInverse</item>
+        <item name="android:colorPrimaryDark">#E8EAED</item>
+        <item name="android:textColorSecondary">?android:attr/textColorPrimary</item>
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
     </style>
-
-    <style name="WidgetContainerTheme.Dark" />
+    <style name="WidgetContainerTheme.Dark" parent="AppTheme.Dark">
+        <item name="android:colorEdgeEffect">?android:attr/textColorSecondary</item>
+        <item name="android:colorPrimaryDark">#616161</item> <!-- Gray 700 -->
+    </style>
 
     <style name="FastScrollerPopup" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
         <item name="android:layout_width">wrap_content</item>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 3455cb8..7e72208 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -44,12 +44,6 @@
         android:defaultValue="@bool/allow_rotation"
         android:persistent="true" />
 
-    <SwitchPreference
-        android:key="pref_grid_options"
-        android:title="Enable grid options"
-        android:defaultValue="false"
-        android:persistent="true" />
-
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
         android:persistent="false"
diff --git a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
index 5610b0e..9ac3fe7 100644
--- a/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/CacheDataUpdatedTaskTest.java
@@ -53,10 +53,10 @@
         mModelHelper = new LauncherModelHelper();
         mModelHelper.initializeData("/cache_data_updated_task_data.txt");
 
-        // Add dummy entries in the cache to simulate update
+        // Add placeholder entries in the cache to simulate update
         Context context = RuntimeEnvironment.application;
         IconCache iconCache = LauncherAppState.getInstance(context).getIconCache();
-        CachingLogic<ItemInfo> dummyLogic = new CachingLogic<ItemInfo>() {
+        CachingLogic<ItemInfo> placeholderLogic = new CachingLogic<ItemInfo>() {
             @Override
             public ComponentName getComponent(ItemInfo info) {
                 return info.getTargetComponent();
@@ -81,7 +81,7 @@
 
         UserManager um = context.getSystemService(UserManager.class);
         for (ItemInfo info : mModelHelper.getBgDataModel().itemsIdMap) {
-            iconCache.addIconToDBAndMemCache(info, dummyLogic, new PackageInfo(),
+            iconCache.addIconToDBAndMemCache(info, placeholderLogic, new PackageInfo(),
                     um.getSerialNumberForUser(info.user), true);
         }
     }
diff --git a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index bbbe21e..be03c7d 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -165,7 +165,7 @@
             @Override
             public void onOpen(SQLiteDatabase db) { }
         };
-        // Insert dummy data
+        // Insert mock data
         for (int i = 0; i < 10; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
diff --git a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 8f3a83e..655237d 100644
--- a/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -109,7 +109,7 @@
     public void testCustomProfileLoaded_with_widget() throws Exception {
         String pendingAppPkg = "com.test.pending";
 
-        // Add a dummy session info so that the widget exists
+        // Add a placeholder session info so that the widget exists
         SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
         params.setAppPackageName(pendingAppPkg);
 
@@ -120,7 +120,7 @@
         setField(sessionInfo, "appIcon", BitmapInfo.LOW_RES_ICON);
 
         writeLayoutAndLoad(new LauncherLayoutBuilder().atWorkspace(0, 1, 0)
-                .putWidget(pendingAppPkg, "DummyWidget", 2, 2));
+                .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
 
         // Verify widget
         assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
diff --git a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
index 3a252dc..fb08c56 100644
--- a/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -51,7 +51,7 @@
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
@@ -92,9 +92,9 @@
                 SCREEN, CELLX, CELLY, RESTORED, INTENT
         });
 
-        mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp,
-                new UserManagerState());
-        mLoaderCursor.allUsers.put(0, Process.myUserHandle());
+        UserManagerState ums = new UserManagerState();
+        mLoaderCursor = new LoaderCursor(mCursor, Favorites.CONTENT_URI, mApp, ums);
+        ums.allUsers.put(0, Process.myUserHandle());
     }
 
     private void initCursor(int itemType, String title) {
@@ -110,7 +110,7 @@
     public void getAppShortcutInfo_dontAllowMissing_invalidComponent() {
         initCursor(ITEM_TYPE_APPLICATION, "");
         assertTrue(mLoaderCursor.moveToNext());
-        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do");
         assertNull(mLoaderCursor.getAppShortcutInfo(
                 new Intent().setComponent(cn), false /* allowMissingTarget */, true));
     }
@@ -136,7 +136,7 @@
         initCursor(ITEM_TYPE_APPLICATION, "");
         assertTrue(mLoaderCursor.moveToNext());
 
-        ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do");
+        ComponentName cn = new ComponentName(mContext.getPackageName(), "placeholder-do");
         WorkspaceItemInfo info = Executors.MODEL_EXECUTOR.submit(() ->
                 mLoaderCursor.getAppShortcutInfo(
                         new Intent().setComponent(cn), true  /* allowMissingTarget */, true))
diff --git a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index ee73b82..4184d33 100644
--- a/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/robolectric_tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -44,7 +44,7 @@
     @Test
     public void testMigrateProfileId() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
-        // Add some dummy data
+        // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
@@ -64,7 +64,7 @@
     @Test
     public void testChangeDefaultColumn() throws Exception {
         SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
-        // Add some dummy data
+        // Add some mock data
         for (int i = 0; i < 5; i++) {
             ContentValues values = new ContentValues();
             values.put(Favorites._ID, i);
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 0388087..f2b3071 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -81,7 +81,7 @@
     public static final int NO__ICON = -1;
     public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
 
-    // Authority for providing a dummy default-workspace-layout data.
+    // Authority for providing a test default-workspace-layout data.
     private static final String TEST_PROVIDER_AUTHORITY =
             LauncherModelHelper.class.getName().toLowerCase();
     private static final int DEFAULT_BITMAP_SIZE = 10;
@@ -252,7 +252,7 @@
     }
 
     /**
-     * Adds a dummy item in the DB.
+     * Adds a mock item in the DB.
      * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
      *             folder (where the type represents the number of items in the folder).
      */
@@ -310,7 +310,7 @@
     }
 
     /**
-     * Initializes the DB with dummy elements to represent the provided grid structure.
+     * Initializes the DB with mock elements to represent the provided grid structure.
      * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
      *                  type definitions. The first dimension represents the screens and the next
      *                  two represent the workspace grid.
@@ -347,7 +347,7 @@
     }
 
     /**
-     * Sets up a dummy provider to load the provided layout by default, next time the layout loads
+     * Sets up a mock provider to load the provided layout by default, next time the layout loads
      */
     public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
             throws Exception {
@@ -360,7 +360,7 @@
                 "launcher3.layout.provider", TEST_PROVIDER_AUTHORITY);
 
         shadowOf(context.getPackageManager())
-                .addProviderIfNotPresent(new ComponentName("com.test", "Dummy")).authority =
+                .addProviderIfNotPresent(new ComponentName("com.test", "Mock")).authority =
                 TEST_PROVIDER_AUTHORITY;
 
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
diff --git a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
index 84c65b1..5ab3106 100644
--- a/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
+++ b/robolectric_tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java
@@ -129,7 +129,7 @@
         ShadowPackageManager spm = shadowOf(mContext.getPackageManager());
 
         for (int i = 0; i < num; i++) {
-            ComponentName cn = new ComponentName("com.dummy.apk" + i, "DummyWidet");
+            ComponentName cn = new ComponentName("com.placeholder.apk" + i, "PlaceholderWidet");
 
             AppWidgetProviderInfo widgetInfo = new AppWidgetProviderInfo();
             widgetInfo.provider = cn;
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index ce37a30..3c34444 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
@@ -35,8 +36,6 @@
 import androidx.annotation.IntDef;
 
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
@@ -126,10 +125,9 @@
     }
 
     public final void close(boolean animate) {
-        animate &= Utilities.areAnimationsEnabled(getContext());
+        animate &= areAnimatorsEnabled();
         if (mIsOpen) {
-            BaseActivity.fromContext(getContext()).getUserEventDispatcher()
-                    .resetElapsedContainerMillis("container closed");
+            // Add to WW logging
         }
         handleClose(animate);
         mIsOpen = false;
@@ -144,12 +142,6 @@
     public void addHintCloseAnim(
             float distanceToMove, Interpolator interpolator, PendingAnimation target) { }
 
-    public abstract void logActionCommand(int command);
-
-    public int getLogContainerType() {
-        return ContainerType.DEFAULT_CONTAINERTYPE;
-    }
-
     public final boolean isOpen() {
         return mIsOpen;
     }
@@ -158,7 +150,6 @@
 
     /** @return Whether the back is consumed. If false, Launcher will handle the back as well. */
     public boolean onBackPressed() {
-        logActionCommand(Action.Command.BACK);
         close(true);
         return true;
     }
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index 9b6166f..3db456c 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -3,15 +3,25 @@
 import android.content.ComponentName;
 import android.content.Context;
 
-import com.android.launcher3.util.ResourceBasedOverride;
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
 
-public class AppFilter implements ResourceBasedOverride {
+/**
+ * Utility class to filter out components from various lists
+ */
+public class AppFilter {
 
-    public static AppFilter newInstance(Context context) {
-        return Overrides.getObject(AppFilter.class, context, R.string.app_filter_class);
+    private final Set<ComponentName> mFilteredComponents;
+
+    public AppFilter(Context context) {
+        mFilteredComponents = Arrays.stream(
+                context.getResources().getStringArray(R.array.filtered_components))
+                .map(ComponentName::unflattenFromString)
+                .collect(Collectors.toSet());
     }
 
     public boolean shouldShowApp(ComponentName app) {
-        return true;
+        return !mFilteredComponents.contains(app);
     }
 }
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 6f7b684..168d9c4 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -537,11 +537,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO: Log this case.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_WIDGET_RESIZE_FRAME) != 0;
     }
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 432073e..f61bc05 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -658,7 +658,7 @@
         }
     }
 
-    protected static void beginDocument(XmlPullParser parser, String firstElementName)
+    public static void beginDocument(XmlPullParser parser, String firstElementName)
             throws XmlPullParserException, IOException {
         int type;
         while ((type = parser.next()) != XmlPullParser.START_TAG
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 112126b..0d90602 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
-import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+import static com.android.launcher3.util.DisplayController.DisplayHolder.CHANGE_ROTATION;
 
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
@@ -42,6 +42,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
@@ -49,9 +50,9 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.DisplayInfoChangeListener;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
@@ -83,7 +84,7 @@
 
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
+        DisplayController.getDefaultDisplay(this).addChangeListener(this);
 
         // Update theme
         WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
@@ -107,10 +108,20 @@
 
     private void updateTheme() {
         if (mThemeRes != Themes.getActivityThemeRes(this)) {
-            recreate();
+            // Workaround (b/162812884): The system currently doesn't allow recreating an activity
+            // when it is not resumed, in such a case defer recreation until it is possible
+            if (hasBeenResumed()) {
+                recreate();
+            } else {
+                addOnResumeCallback(this::recreate);
+            }
         }
     }
 
+    protected void addOnResumeCallback(OnResumeCallback callback) {
+        // To be overridden
+    }
+
     @Override
     public void onActionModeStarted(ActionMode mode) {
         super.onActionModeStarted(mode);
@@ -182,7 +193,6 @@
                 getSystemService(LauncherApps.class).startMainActivity(
                         intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
             }
-            getUserEventDispatcher().logAppLaunch(v, intent, user);
             if (item != null) {
                 InstanceId instanceId = new InstanceIdSequence().newInstanceId();
                 logAppLaunch(item, instanceId);
@@ -246,7 +256,7 @@
     protected void onDestroy() {
         super.onDestroy();
         WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
-        DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
+        DisplayController.getDefaultDisplay(this).removeChangeListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 198f13d..06bb263 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -77,8 +77,9 @@
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
     private static final int DISPLAY_FOLDER = 2;
+    private static final int DISPLAY_HERO_APP = 5;
 
-    private static final int[] STATE_PRESSED = new int[] {android.R.attr.state_pressed};
+    private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -178,6 +179,8 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
+        } else if (mDisplay == DISPLAY_HERO_APP) {
+            defaultIconSize = grid.allAppsIconSizePx;
         } else {
             // widget_selection or shortcut_popup
             defaultIconSize = grid.iconSizePx;
@@ -277,7 +280,10 @@
         applyDotState(info, false /* animate */);
     }
 
-    public void applyFromPackageItemInfo(PackageItemInfo info) {
+    /**
+     * Apply label and tag using a generic {@link ItemInfoWithIcon}
+     */
+    public void applyFromItemInfoWithIcon(ItemInfoWithIcon info) {
         applyIconAndLabel(info);
         // We don't need to check the info since it's not a WorkspaceItemInfo
         super.setTag(info);
@@ -404,12 +410,14 @@
 
     /**
      * Draws the notification dot in the top right corner of the icon bounds.
+     *
      * @param canvas The canvas to draw to.
      */
     protected void drawDotIfNecessary(Canvas canvas) {
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
-            Utilities.scaleRectAboutCenter(mDotParams.iconBounds, IconShape.getNormalizationScale());
+            Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
+                    IconShape.getNormalizationScale());
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
@@ -504,6 +512,7 @@
 
     /**
      * Creates an animator to fade the text in or out.
+     *
      * @param fadeIn Whether the text should fade in or fade out.
      */
     public ObjectAnimator createTextAlphaAnimator(boolean fadeIn) {
@@ -660,7 +669,7 @@
                 applyFromWorkspaceItem((WorkspaceItemInfo) info);
                 mActivity.invalidateParent(info);
             } else if (info instanceof PackageItemInfo) {
-                applyFromPackageItemInfo((PackageItemInfo) info);
+                applyFromItemInfoWithIcon((PackageItemInfo) info);
             }
 
             mDisableRelayout = false;
@@ -758,7 +767,8 @@
     public SafeCloseable prepareDrawDragView() {
         resetIconScale();
         setForceHideDot(true);
-        return () -> { };
+        return () -> {
+        };
     }
 
     private void resetIconScale() {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 89d768c..1cd201f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5;
 
 import android.animation.Animator;
@@ -2009,7 +2011,7 @@
             // Animations are disabled in power save mode, causing the repeated animation to jump
             // spastically between beginning and end states. Since this looks bad, we don't repeat
             // the animation in power save mode.
-            if (Utilities.areAnimationsEnabled(getContext())) {
+            if (areAnimatorsEnabled()) {
                 va.setRepeatMode(ValueAnimator.REVERSE);
                 va.setRepeatCount(ValueAnimator.INFINITE);
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 49caa93..12ce9f3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -28,7 +28,8 @@
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.WindowBounds;
 
 public class DeviceProfile {
@@ -38,7 +39,7 @@
 
 
     public final InvariantDeviceProfile inv;
-    private final DefaultDisplay.Info mInfo;
+    private final Info mInfo;
 
     // Device properties
     public final boolean isTablet;
@@ -140,7 +141,7 @@
     public DotRenderer mDotRendererWorkSpace;
     public DotRenderer mDotRendererAllApps;
 
-    DeviceProfile(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info,
+    DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
             Point minSize, Point maxSize, int width, int height, boolean isLandscape,
             boolean isMultiWindowMode, boolean transposeLayoutWithOrientation,
             Point windowPosition) {
@@ -606,7 +607,7 @@
      */
     public boolean updateIsSeascape(Context context) {
         if (isVerticalBarLayout()) {
-            boolean isSeascape = DefaultDisplay.INSTANCE.get(context).getInfo().rotation
+            boolean isSeascape = DisplayController.getDefaultDisplay(context).getInfo().rotation
                     == Surface.ROTATION_270;
             if (mIsSeascape != isSeascape) {
                 mIsSeascape = isSeascape;
@@ -638,7 +639,7 @@
         }
     }
 
-    private static Context getContext(Context c, DefaultDisplay.Info info, int orientation) {
+    private static Context getContext(Context c, Info info, int orientation) {
         Configuration config = new Configuration(c.getResources().getConfiguration());
         config.orientation = orientation;
         config.densityDpi = info.metrics.densityDpi;
@@ -662,7 +663,7 @@
     public static class Builder {
         private Context mContext;
         private InvariantDeviceProfile mInv;
-        private DefaultDisplay.Info mInfo;
+        private Info mInfo;
 
         private final Point mWindowPosition = new Point();
         private Point mMinSize, mMaxSize;
@@ -672,7 +673,7 @@
         private boolean mIsMultiWindowMode = false;
         private boolean mTransposeLayoutWithOrientation;
 
-        public Builder(Context context, InvariantDeviceProfile inv, DefaultDisplay.Info info) {
+        public Builder(Context context, InvariantDeviceProfile inv, Info info) {
             mContext = context;
             mInv = inv;
             mInfo = info;
diff --git a/src/com/android/launcher3/DragSource.java b/src/com/android/launcher3/DragSource.java
index d4d7b99..ba227d4 100644
--- a/src/com/android/launcher3/DragSource.java
+++ b/src/com/android/launcher3/DragSource.java
@@ -19,12 +19,11 @@
 import android.view.View;
 
 import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 
 /**
  * Interface defining an object that can originate a drag.
  */
-public interface DragSource extends LogContainerProvider {
+public interface DragSource {
 
     /**
      * A callback made back to the source after an item from this source has been dropped on a
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index b27abc4..fd4c30c 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -113,18 +113,6 @@
 
             return res;
         }
-
-
-        /**
-         * This is used to determine if an object is dropped at a different location than it was
-         * dragged from
-         */
-        public boolean isMoved() {
-            return dragInfo.cellX != originalDragInfo.cellX
-                    || dragInfo.cellY != originalDragInfo.cellY
-                    || dragInfo.screenId != originalDragInfo.screenId
-                    || dragInfo.container != originalDragInfo.container;
-        }
     }
 
     /**
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index d64967b..02c6162 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -24,6 +24,7 @@
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.UiThreadHelper;
 
 
@@ -130,6 +131,10 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
+        } else {
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                return;
+            }
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/FirstFrameAnimatorHelper.java b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
index 6c5bc40..a199a57 100644
--- a/src/com/android/launcher3/FirstFrameAnimatorHelper.java
+++ b/src/com/android/launcher3/FirstFrameAnimatorHelper.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
deleted file mode 100644
index 62c9b4d..0000000
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ /dev/null
@@ -1,672 +0,0 @@
-/*
- * Copyright (C) 2008 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 static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ActivityInfo;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageManager;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.os.Parcelable;
-import android.os.Process;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.LauncherIcons;
-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.pm.UserCache;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.Thunk;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-public class InstallShortcutReceiver extends BroadcastReceiver {
-
-    public static final int FLAG_ACTIVITY_PAUSED = 1;
-    public static final int FLAG_LOADER_RUNNING = 2;
-    public static final int FLAG_DRAG_AND_DROP = 4;
-
-    // Determines whether to defer installing shortcuts immediately until
-    // processAllPendingInstalls() is called.
-    private static int sInstallQueueDisabledFlags = 0;
-
-    private static final String TAG = "InstallShortcutReceiver";
-    private static final boolean DBG = false;
-
-    private static final String ACTION_INSTALL_SHORTCUT =
-            "com.android.launcher.action.INSTALL_SHORTCUT";
-
-    private static final String LAUNCH_INTENT_KEY = "intent.launch";
-    private static final String NAME_KEY = "name";
-    private static final String ICON_KEY = "icon";
-    private static final String ICON_RESOURCE_NAME_KEY = "iconResource";
-    private static final String ICON_RESOURCE_PACKAGE_NAME_KEY = "iconResourcePackage";
-
-    private static final String APP_SHORTCUT_TYPE_KEY = "isAppShortcut";
-    private static final String DEEPSHORTCUT_TYPE_KEY = "isDeepShortcut";
-    private static final String APP_WIDGET_TYPE_KEY = "isAppWidget";
-    private static final String USER_HANDLE_KEY = "userHandle";
-
-    // The set of shortcuts that are pending install
-    private static final String APPS_PENDING_INSTALL = "apps_to_install";
-
-    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
-    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
-
-    @WorkerThread
-    private static void addToQueue(Context context, PendingInstallShortcutInfo info) {
-        String encoded = info.encodeToString();
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
-        strings.add(encoded);
-        prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
-    }
-
-    @WorkerThread
-    private static void flushQueueInBackground(Context context) {
-        if (Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null) {
-            // Launcher not loaded
-            return;
-        }
-
-        ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
-        if (strings == null) {
-            return;
-        }
-
-        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-        for (String encoded : strings) {
-            PendingInstallShortcutInfo info = decode(encoded, context);
-            if (info == null) {
-                continue;
-            }
-
-            String pkg = getIntentPackage(info.launchIntent);
-            if (!TextUtils.isEmpty(pkg)
-                    && !launcherApps.isPackageEnabled(pkg, info.user)
-                    && !info.isActivity) {
-                if (DBG) {
-                    Log.d(TAG, "Ignoring shortcut for absent package: " + info.launchIntent);
-                }
-                continue;
-            }
-
-            // Generate a shortcut info to add into the model
-            installQueue.add(info.getItemInfo());
-        }
-        prefs.edit().remove(APPS_PENDING_INSTALL).apply();
-        if (!installQueue.isEmpty()) {
-            LauncherAppState.getInstance(context).getModel()
-                    .addAndBindAddedWorkspaceItems(installQueue);
-        }
-    }
-
-    public static void removeFromInstallQueue(Context context, HashSet<String> packageNames,
-            UserHandle user) {
-        if (packageNames.isEmpty()) {
-            return;
-        }
-        Preconditions.assertWorkerThread();
-
-        SharedPreferences sp = Utilities.getPrefs(context);
-        Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
-        if (DBG) {
-            Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
-                    + ", removing packages: " + packageNames);
-        }
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return;
-        }
-        Set<String> newStrings = new HashSet<>(strings);
-        Iterator<String> newStringsIter = newStrings.iterator();
-        while (newStringsIter.hasNext()) {
-            String encoded = newStringsIter.next();
-            try {
-                Decoder decoder = new Decoder(encoded, context);
-                if (packageNames.contains(getIntentPackage(decoder.launcherIntent)) &&
-                        user.equals(decoder.user)) {
-                    newStringsIter.remove();
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-                newStringsIter.remove();
-            }
-        }
-        sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
-    }
-
-    public void onReceive(Context context, Intent data) {
-        if (!ACTION_INSTALL_SHORTCUT.equals(data.getAction())) {
-            return;
-        }
-        PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        if (info != null) {
-            if (!info.isLauncherActivity()) {
-                // Since its a custom shortcut, verify that it is safe to launch.
-                if (!new PackageManagerHelper(context).hasPermissionForActivity(
-                        info.launchIntent, null)) {
-                    // Target cannot be launched, or requires some special permission to launch
-                    Log.e(TAG, "Ignoring malicious intent " + info.launchIntent.toUri(0));
-                    return;
-                }
-            }
-            queuePendingShortcutInfo(info, context);
-        }
-    }
-
-    /**
-     * @return true is the extra is either null or is of type {@param type}
-     */
-    private static boolean isValidExtraType(Intent intent, String key, Class type) {
-        Object extra = intent.getParcelableExtra(key);
-        return extra == null || type.isInstance(extra);
-    }
-
-    /**
-     * Verifies the intent and creates a {@link PendingInstallShortcutInfo}
-     */
-    private static PendingInstallShortcutInfo createPendingInfo(Context context, Intent data) {
-        if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class) ||
-                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
-                        Intent.ShortcutIconResource.class)) ||
-                !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
-
-            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
-            return null;
-        }
-
-        PendingInstallShortcutInfo info = new PendingInstallShortcutInfo(
-                data, Process.myUserHandle(), context);
-        if (info.launchIntent == null || info.label == null) {
-            if (DBG) Log.e(TAG, "Invalid install shortcut intent");
-            return null;
-        }
-
-        return convertToLauncherActivityIfPossible(info);
-    }
-
-    public static WorkspaceItemInfo fromShortcutIntent(Context context, Intent data) {
-        PendingInstallShortcutInfo info = createPendingInfo(context, data);
-        return info == null ? null : (WorkspaceItemInfo) info.getItemInfo().first;
-    }
-
-    public static void queueShortcut(ShortcutInfo info, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, context), context);
-    }
-
-    public static void queueWidget(AppWidgetProviderInfo info, int widgetId, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId, context), context);
-    }
-
-    public static void queueApplication(Intent data, UserHandle user, Context context) {
-        queuePendingShortcutInfo(new PendingInstallShortcutInfo(data, context, user),
-                context);
-    }
-
-    public static HashSet<ShortcutKey> getPendingShortcuts(Context context) {
-        HashSet<ShortcutKey> result = new HashSet<>();
-
-        Set<String> strings = Utilities.getPrefs(context).getStringSet(APPS_PENDING_INSTALL, null);
-        if (strings == null || ((Collection) strings).isEmpty()) {
-            return result;
-        }
-
-        for (String encoded : strings) {
-            try {
-                Decoder decoder = new Decoder(encoded, context);
-                if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
-                    result.add(ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user));
-                }
-            } catch (JSONException | URISyntaxException e) {
-                Log.d(TAG, "Exception reading shortcut to add: " + e);
-            }
-        }
-        return result;
-    }
-
-    private static void queuePendingShortcutInfo(PendingInstallShortcutInfo info, Context context) {
-        // Queue the item up for adding if launcher has not loaded properly yet
-        MODEL_EXECUTOR.post(() -> addToQueue(context, info));
-        flushInstallQueue(context);
-    }
-
-    public static void enableInstallQueue(int flag) {
-        sInstallQueueDisabledFlags |= flag;
-    }
-    public static void disableAndFlushInstallQueue(int flag, Context context) {
-        sInstallQueueDisabledFlags &= ~flag;
-        flushInstallQueue(context);
-    }
-
-    static void flushInstallQueue(Context context) {
-        if (sInstallQueueDisabledFlags != 0) {
-            return;
-        }
-        MODEL_EXECUTOR.post(() -> flushQueueInBackground(context));
-    }
-
-    /**
-     * Ensures that we have a valid, non-null name.  If the provided name is null, we will return
-     * the application name instead.
-     */
-    @Thunk static CharSequence ensureValidName(Context context, Intent intent, CharSequence name) {
-        if (name == null) {
-            try {
-                PackageManager pm = context.getPackageManager();
-                ActivityInfo info = pm.getActivityInfo(intent.getComponent(), 0);
-                name = info.loadLabel(pm);
-            } catch (PackageManager.NameNotFoundException nnfe) {
-                return "";
-            }
-        }
-        return name;
-    }
-
-    private static class PendingInstallShortcutInfo {
-
-        final boolean isActivity;
-        @Nullable final ShortcutInfo shortcutInfo;
-        @Nullable final AppWidgetProviderInfo providerInfo;
-
-        @Nullable final Intent data;
-        final Context mContext;
-        final Intent launchIntent;
-        final String label;
-        final UserHandle user;
-
-        /**
-         * Initializes a PendingInstallShortcutInfo received from a different app.
-         */
-        public PendingInstallShortcutInfo(Intent data, UserHandle user, Context context) {
-            isActivity = false;
-            shortcutInfo = null;
-            providerInfo = null;
-
-            this.data = data;
-            this.user = user;
-            mContext = context;
-
-            launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-            label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(LauncherActivityInfo info, Context context) {
-            isActivity = true;
-            shortcutInfo = null;
-            providerInfo = null;
-
-            String packageName = info.getComponentName().getPackageName();
-            data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
-                    new ComponentName(packageName, "")).setPackage(packageName));
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, info.getLabel());
-
-            user = info.getUser();
-            mContext = context;
-
-            launchIntent = AppInfo.makeLaunchIntent(info);
-            label = info.getLabel().toString();
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(Intent data, Context context, UserHandle user) {
-            isActivity = true;
-            shortcutInfo = null;
-            providerInfo = null;
-
-            this.data = data;
-            this.user = user;
-            mContext = context;
-
-            launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-            label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(ShortcutInfo info, Context context) {
-            isActivity = false;
-            shortcutInfo = info;
-            providerInfo = null;
-
-            data = null;
-            mContext = context;
-            user = info.getUserHandle();
-
-            launchIntent = ShortcutKey.makeIntent(info);
-            label = info.getShortLabel().toString();
-        }
-
-        /**
-         * Initializes a PendingInstallShortcutInfo to represent a launcher target.
-         */
-        public PendingInstallShortcutInfo(
-                AppWidgetProviderInfo info, int widgetId, Context context) {
-            isActivity = false;
-            shortcutInfo = null;
-            providerInfo = info;
-
-            data = null;
-            mContext = context;
-            user = info.getProfile();
-
-            launchIntent = new Intent().setComponent(info.provider)
-                    .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
-            label = info.label;
-        }
-
-        public String encodeToString() {
-            try {
-                if (shortcutInfo != null) {
-                    // If it a launcher target, we only need component name, and user to
-                    // recreate this.
-                    return new JSONStringer()
-                            .object()
-                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                            .key(DEEPSHORTCUT_TYPE_KEY).value(true)
-                            .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
-                                    .getSerialNumberForUser(user))
-                            .endObject().toString();
-                } else if (providerInfo != null) {
-                    // If it a launcher target, we only need component name, and user to
-                    // recreate this.
-                    return new JSONStringer()
-                            .object()
-                            .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                            .key(APP_WIDGET_TYPE_KEY).value(true)
-                            .key(USER_HANDLE_KEY).value(UserCache.INSTANCE.get(mContext)
-                                    .getSerialNumberForUser(user))
-                            .endObject().toString();
-                }
-
-                if (launchIntent.getAction() == null) {
-                    launchIntent.setAction(Intent.ACTION_VIEW);
-                } else if (launchIntent.getAction().equals(Intent.ACTION_MAIN) &&
-                        launchIntent.getCategories() != null &&
-                        launchIntent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
-                    launchIntent.addFlags(
-                            Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-                }
-
-                // This name is only used for comparisons and notifications, so fall back to activity
-                // name if not supplied
-                String name = ensureValidName(mContext, launchIntent, label).toString();
-                Bitmap icon = data == null ? null
-                        : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-                Intent.ShortcutIconResource iconResource = data == null ? null
-                    : data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-
-                // Only encode the parameters which are supported by the API.
-                JSONStringer json = new JSONStringer()
-                    .object()
-                    .key(LAUNCH_INTENT_KEY).value(launchIntent.toUri(0))
-                    .key(NAME_KEY).value(name)
-                    .key(USER_HANDLE_KEY).value(
-                            UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user))
-                    .key(APP_SHORTCUT_TYPE_KEY).value(isActivity);
-                if (icon != null) {
-                    byte[] iconByteArray = GraphicsUtils.flattenBitmap(icon);
-                    if (iconByteArray != null) {
-                        json = json.key(ICON_KEY).value(
-                                Base64.encodeToString(
-                                        iconByteArray, 0, iconByteArray.length, Base64.DEFAULT));
-                    }
-                }
-                if (iconResource != null) {
-                    json = json.key(ICON_RESOURCE_NAME_KEY).value(iconResource.resourceName);
-                    json = json.key(ICON_RESOURCE_PACKAGE_NAME_KEY)
-                            .value(iconResource.packageName);
-                }
-                return json.endObject().toString();
-            } catch (JSONException e) {
-                Log.d(TAG, "Exception when adding shortcut: " + e);
-                return null;
-            }
-        }
-
-        public Pair<ItemInfo, Object> getItemInfo() {
-            if (isActivity) {
-                WorkspaceItemInfo si = createWorkspaceItemInfo(data, user,
-                        LauncherAppState.getInstance(mContext));
-                si.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
-                si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
-                return Pair.create(si, null);
-            } else if (shortcutInfo != null) {
-                WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
-                LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(
-                        itemInfo, shortcutInfo);
-                return Pair.create(itemInfo, shortcutInfo);
-            } else if (providerInfo != null) {
-                LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
-                        .fromProviderInfo(mContext, providerInfo);
-                LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
-                        launchIntent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
-                        info.provider);
-                InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
-                widgetInfo.minSpanX = info.minSpanX;
-                widgetInfo.minSpanY = info.minSpanY;
-                widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
-                widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
-                return Pair.create(widgetInfo, providerInfo);
-            } else {
-                WorkspaceItemInfo itemInfo =
-                        createWorkspaceItemInfo(data, user, LauncherAppState.getInstance(mContext));
-                return Pair.create(itemInfo, null);
-            }
-        }
-
-        public boolean isLauncherActivity() {
-            return isActivity;
-        }
-    }
-
-    private static String getIntentPackage(Intent intent) {
-        return intent.getComponent() == null
-                ? intent.getPackage() : intent.getComponent().getPackageName();
-    }
-
-    private static PendingInstallShortcutInfo decode(String encoded, Context context) {
-        try {
-            Decoder decoder = new Decoder(encoded, context);
-            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                LauncherActivityInfo info = context.getSystemService(LauncherApps.class)
-                        .resolveActivity(decoder.launcherIntent, decoder.user);
-                if (info != null) {
-                    return new PendingInstallShortcutInfo(info, context);
-                }
-            } else if (decoder.optBoolean(DEEPSHORTCUT_TYPE_KEY)) {
-                List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.launcherIntent, decoder.user)
-                        .buildRequest(context)
-                        .query(ShortcutRequest.ALL);
-                if (si.isEmpty()) {
-                    return null;
-                } else {
-                    return new PendingInstallShortcutInfo(si.get(0), context);
-                }
-            } else if (decoder.optBoolean(APP_WIDGET_TYPE_KEY)) {
-                int widgetId = decoder.launcherIntent
-                        .getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0);
-                AppWidgetProviderInfo info = AppWidgetManager.getInstance(context)
-                        .getAppWidgetInfo(widgetId);
-                if (info == null || !info.provider.equals(decoder.launcherIntent.getComponent()) ||
-                        !info.getProfile().equals(decoder.user)) {
-                    return null;
-                }
-                return new PendingInstallShortcutInfo(info, widgetId, context);
-            }
-
-            Intent data = new Intent();
-            data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, decoder.launcherIntent);
-            data.putExtra(Intent.EXTRA_SHORTCUT_NAME, decoder.getString(NAME_KEY));
-
-            String iconBase64 = decoder.optString(ICON_KEY);
-            String iconResourceName = decoder.optString(ICON_RESOURCE_NAME_KEY);
-            String iconResourcePackageName = decoder.optString(ICON_RESOURCE_PACKAGE_NAME_KEY);
-            if (iconBase64 != null && !iconBase64.isEmpty()) {
-                byte[] iconArray = Base64.decode(iconBase64, Base64.DEFAULT);
-                Bitmap b = BitmapFactory.decodeByteArray(iconArray, 0, iconArray.length);
-                data.putExtra(Intent.EXTRA_SHORTCUT_ICON, b);
-            } else if (iconResourceName != null && !iconResourceName.isEmpty()) {
-                Intent.ShortcutIconResource iconResource =
-                    new Intent.ShortcutIconResource();
-                iconResource.resourceName = iconResourceName;
-                iconResource.packageName = iconResourcePackageName;
-                data.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
-            }
-
-            if (decoder.optBoolean(APP_SHORTCUT_TYPE_KEY)) {
-                return new PendingInstallShortcutInfo(data, context, decoder.user);
-            } else {
-                return new PendingInstallShortcutInfo(data, decoder.user, context);
-            }
-        } catch (JSONException | URISyntaxException e) {
-            Log.d(TAG, "Exception reading shortcut to add: " + e);
-        }
-        return null;
-    }
-
-    private static class Decoder extends JSONObject {
-        public final Intent launcherIntent;
-        public final UserHandle user;
-
-        private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
-            super(encoded);
-            launcherIntent = Intent.parseUri(getString(LAUNCH_INTENT_KEY), 0);
-            user = has(USER_HANDLE_KEY) ? UserCache.INSTANCE.get(context)
-                    .getUserForSerialNumber(getLong(USER_HANDLE_KEY))
-                    : Process.myUserHandle();
-            if (user == null) {
-                throw new JSONException("Invalid user");
-            }
-        }
-    }
-
-    /**
-     * Tries to create a new PendingInstallShortcutInfo which represents the same target,
-     * but is an app target and not a shortcut.
-     * @return the newly created info or the original one.
-     */
-    private static PendingInstallShortcutInfo convertToLauncherActivityIfPossible(
-            PendingInstallShortcutInfo original) {
-        if (original.isLauncherActivity()) {
-            // Already an activity target
-            return original;
-        }
-        if (!PackageManagerHelper.isLauncherAppTarget(original.launchIntent)) {
-            return original;
-        }
-
-        LauncherActivityInfo info = original.mContext.getSystemService(LauncherApps.class)
-                .resolveActivity(original.launchIntent, original.user);
-        if (info == null) {
-            return original;
-        }
-        // Ignore any conflicts in the label name, as that can change based on locale.
-        return new PendingInstallShortcutInfo(info, original.mContext);
-    }
-
-    private static WorkspaceItemInfo createWorkspaceItemInfo(Intent data, UserHandle user,
-            LauncherAppState app) {
-        if (data == null) {
-            Log.e(TAG, "Can't construct WorkspaceItemInfo with null data");
-            return null;
-        }
-
-        Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
-        String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
-        Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
-
-        if (intent == null) {
-            // If the intent is null, return null as we can't construct a valid WorkspaceItemInfo
-            Log.e(TAG, "Can't construct WorkspaceItemInfo with null intent");
-            return null;
-        }
-
-        final WorkspaceItemInfo info = new WorkspaceItemInfo();
-        info.user = user;
-
-        BitmapInfo iconInfo = null;
-        LauncherIcons li = LauncherIcons.obtain(app.getContext());
-        if (bitmap instanceof Bitmap) {
-            iconInfo = li.createIconBitmap((Bitmap) bitmap);
-        } else {
-            Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
-            if (extra instanceof Intent.ShortcutIconResource) {
-                info.iconResource = (Intent.ShortcutIconResource) extra;
-                iconInfo = li.createIconBitmap(info.iconResource);
-            }
-        }
-        li.recycle();
-
-        if (iconInfo == null) {
-            iconInfo = app.getIconCache().getDefaultIcon(info.user);
-        }
-        info.bitmap = iconInfo;
-
-        info.title = Utilities.trim(name);
-        info.contentDescription = app.getContext().getPackageManager()
-                .getUserBadgedLabel(info.title, info.user);
-        info.intent = intent;
-        return info;
-    }
-
-}
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index e39e89c..370bd6f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -48,8 +48,8 @@
 
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.util.ConfigMonitor;
-import com.android.launcher3.util.DefaultDisplay;
-import com.android.launcher3.util.DefaultDisplay.Info;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Themes;
@@ -198,7 +198,7 @@
 
         // Get the display info based on default display and interpolate it to existing display
         DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
-                DefaultDisplay.INSTANCE.get(context).getInfo(),
+                DisplayController.getDefaultDisplay(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
         Info myInfo = new Info(context, display);
@@ -231,7 +231,7 @@
     }
 
     private String initGrid(Context context, String gridName) {
-        DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+        Info displayInfo = DisplayController.getDefaultDisplay(context).getInfo();
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
 
         DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
@@ -240,7 +240,7 @@
     }
 
     private void initGrid(
-            Context context, DefaultDisplay.Info displayInfo, DisplayOption displayOption) {
+            Context context, Info displayInfo, DisplayOption displayOption) {
         GridOption closestProfile = displayOption.grid;
         numRows = closestProfile.numRows;
         numColumns = closestProfile.numColumns;
@@ -466,7 +466,7 @@
 
     @VisibleForTesting
     static DisplayOption invDistWeightedInterpolate(
-            DefaultDisplay.Info displayInfo, ArrayList<DisplayOption> points) {
+            Info displayInfo, ArrayList<DisplayOption> points) {
         Point smallestSize = new Point(displayInfo.smallestSize);
         Point largestSize = new Point(displayInfo.largestSize);
 
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8c0a2d7..90566f3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -24,7 +24,6 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_ICON_SURFACE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
-import static com.android.launcher3.InstallShortcutReceiver.FLAG_DRAG_AND_DROP;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
@@ -34,7 +33,6 @@
 import static com.android.launcher3.LauncherState.NO_OFFSET;
 import static com.android.launcher3.LauncherState.NO_SCALE;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
@@ -43,6 +41,9 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
 import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
 import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
 import static com.android.launcher3.popup.SystemShortcut.INSTALL;
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -87,14 +88,19 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
 import androidx.annotation.VisibleForTesting;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
 
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -109,7 +115,6 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
-import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.icons.IconCache;
@@ -119,8 +124,9 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.UserEventDispatcher;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.model.ModelUtils;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
@@ -204,7 +210,8 @@
  * Default launcher application.
  */
 public class Launcher extends StatefulActivity<LauncherState> implements LauncherExterns,
-        Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin> {
+        Callbacks, InvariantDeviceProfile.OnIDPChangeListener, PluginListener<OverlayPlugin>,
+        LifecycleOwner {
     public static final String TAG = "Launcher";
 
     public static final ActivityTracker<Launcher> ACTIVITY_TRACKER = new ActivityTracker<>();
@@ -265,6 +272,8 @@
     private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
+    private LifecycleRegistry mLifecycleRegistry;
+
     @Thunk
     Workspace mWorkspace;
     @Thunk
@@ -451,12 +460,12 @@
                 float alpha = 1f - mCurrentAssistantVisibility;
                 if (finalState == NORMAL) {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-                } else if (finalState == OVERVIEW || finalState == OVERVIEW_PEEK) {
+                } else if (finalState == OVERVIEW) {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-                    mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
+                    mScrimView.setAlpha(alpha);
                 } else {
                     mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
-                    mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(1f);
+                    mScrimView.setAlpha(1f);
                 }
             }
         });
@@ -465,6 +474,19 @@
 
         mUserChangedCallbackCloseable = UserCache.INSTANCE.get(this).addUserChangeListener(
                 () -> getStateManager().goToState(NORMAL));
+
+        if (Utilities.ATLEAST_R) {
+            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+        }
+
+        mLifecycleRegistry = new LifecycleRegistry(this);
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycleRegistry;
     }
 
     protected LauncherOverlayManager getDefaultOverlay() {
@@ -551,9 +573,9 @@
         LauncherState state = mStateManager.getState();
         if (state == NORMAL) {
             mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-        } else if (state == OVERVIEW || state == OVERVIEW_PEEK) {
+        } else if (state == OVERVIEW) {
             mAppsView.getAlphaProperty(APPS_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
-            mScrimView.getAlphaProperty(SCRIM_VIEW_ALPHA_CHANNEL_INDEX).setValue(alpha);
+            mScrimView.setAlpha(alpha);
         }
     }
 
@@ -880,6 +902,7 @@
 
     @Override
     protected void onStop() {
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
         super.onStop();
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
@@ -903,22 +926,20 @@
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
     }
 
     @Override
     @CallSuper
     protected void onDeferredResumed() {
         logStopAndResume(Action.Command.RESUME);
-        getUserEventDispatcher().startSession();
-
-        AppLaunchTracker.INSTANCE.get(this).onReturnedToHome();
 
         // Process any items that were added while Launcher was away.
-        InstallShortcutReceiver.disableAndFlushInstallQueue(
-                InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .resumeModelPush(FLAG_ACTIVITY_PAUSED);
 
         // Refresh shortcuts if the permission changed.
-        mModel.refreshShortcutsIfRequired();
+        mModel.validateModelDataOnResume();
 
         // Set the notification listener and fetch updated notifications when we resume
         NotificationListener.setNotificationsChangedListener(mPopupDataProvider);
@@ -929,6 +950,7 @@
     protected void handlePendingActivityRequest() { }
 
     private void logStopAndResume(int command) {
+        if (mPendingExecutor != null) return;
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
         int containerType = mStateManager.getState().containerType;
 
@@ -1009,7 +1031,7 @@
         if (state == SPRING_LOADED) {
             // Prevent any Un/InstallShortcutReceivers from updating the db while we are
             // not on homescreen
-            InstallShortcutReceiver.enableInstallQueue(FLAG_DRAG_AND_DROP);
+            ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_DRAG_AND_DROP);
             getRotationHelper().setCurrentStateRequest(REQUEST_LOCK);
 
             mWorkspace.showPageIndicatorAtCurrentScroll();
@@ -1034,7 +1056,8 @@
 
         if (state == NORMAL) {
             // Re-enable any Un/InstallShortcutReceiver and now process any queued items
-            InstallShortcutReceiver.disableAndFlushInstallQueue(FLAG_DRAG_AND_DROP, this);
+            ItemInstallQueue.INSTANCE.get(this)
+                    .resumeModelPush(FLAG_DRAG_AND_DROP);
 
             // Clear any rotation locks when going to normal state
             getRotationHelper().setCurrentStateRequest(REQUEST_NONE);
@@ -1063,13 +1086,15 @@
         }
 
         TraceHelper.INSTANCE.endSection(traceToken);
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
     }
 
     @Override
     protected void onPause() {
         // Ensure that items added to Launcher are queued until Launcher returns
-        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_ACTIVITY_PAUSED);
+        ItemInstallQueue.INSTANCE.get(this).pauseModelPush(FLAG_ACTIVITY_PAUSED);
 
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
         super.onPause();
         mDragController.cancelDrag();
         mLastTouchUpTime = -1;
@@ -1199,16 +1224,13 @@
         int[] cellXY = mTmpAddItemCellCoordinates;
         CellLayout layout = getCellLayout(container, screenId);
 
-        WorkspaceItemInfo info = null;
-        if (Utilities.ATLEAST_OREO) {
-            info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
+        WorkspaceItemInfo info = PinRequestHelper.createWorkspaceItemFromPinItemRequest(
                     this, PinRequestHelper.getPinItemRequest(data), 0);
-        }
 
         if (info == null) {
             // Legacy shortcuts are only supported for primary profile.
             info = Process.myUserHandle().equals(args.user)
-                    ? InstallShortcutReceiver.fromShortcutIntent(this, data) : null;
+                    ? ModelUtils.fromLegacyShortcutIntent(this, data) : null;
 
             if (info == null) {
                 Log.e(TAG, "Unable to parse a valid custom shortcut result");
@@ -1455,12 +1477,17 @@
             mOverlayManager.hideOverlay(isStarted() && !isForceInvisible());
             handleGestureContract(intent);
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
-            getStateManager().goToState(ALL_APPS, alreadyOnHome);
+            showAllAppsFromIntent(alreadyOnHome);
         }
 
         TraceHelper.INSTANCE.endSection(traceToken);
     }
 
+    protected void showAllAppsFromIntent(boolean alreadyOnHome) {
+        AbstractFloatingView.closeAllOpenViews(this);
+        getStateManager().goToState(ALL_APPS, alreadyOnHome);
+    }
+
     /**
      * Handles gesture nav contract
      */
@@ -1550,7 +1577,7 @@
         mOverlayManager.onActivityDestroyed(this);
         mAppTransitionManager.unregisterRemoteAnimations();
         mUserChangedCallbackCloseable.close();
-        mAllAppsController.onActivityDestroyed();
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1745,16 +1772,6 @@
     }
 
     /**
-     * Called when a workspace item is converted into a folder
-     */
-    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo){}
-
-    /**
-     * Called when a folder is converted into a workspace item
-     */
-    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {}
-
-    /**
      * Unbinds the view for the specified item, and removes the item and all its children.
      *
      * @param v the view being removed.
@@ -1937,11 +1954,12 @@
         // Populate event with a fake title based on the current state.
         // TODO: When can workspace be null?
         text.add(mWorkspace == null
-                ? getString(R.string.all_apps_home_button_label)
+                ? getString(R.string.home_screen)
                 : mStateManager.getState().getDescription(this));
         return result;
     }
 
+    @Override
     public void addOnResumeCallback(OnResumeCallback callback) {
         mOnResumeCallbacks.add(callback);
     }
@@ -2190,9 +2208,6 @@
         workspace.requestLayout();
     }
 
-    @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
-
     /**
      * Add the views for a widget to the workspace.
      */
@@ -2421,8 +2436,8 @@
             mPendingActivityResult = null;
         }
 
-        InstallShortcutReceiver.disableAndFlushInstallQueue(
-                InstallShortcutReceiver.FLAG_LOADER_RUNNING, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .resumeModelPush(FLAG_LOADER_RUNNING);
 
         // When undoing the removal of the last item on a page, return to that page.
         // Since we are just resetting the current page without user interaction,
@@ -2449,8 +2464,8 @@
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
         ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
-                .setDuration(InstallShortcutReceiver.NEW_SHORTCUT_BOUNCE_DURATION);
-        bounceAnim.setStartDelay(i * InstallShortcutReceiver.NEW_SHORTCUT_STAGGER_DELAY);
+                .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
+        bounceAnim.setStartDelay(i * ItemInstallQueue.NEW_SHORTCUT_STAGGER_DELAY);
         bounceAnim.setInterpolator(new OvershootInterpolator(BOUNCE_ANIMATION_TENSION));
         return bounceAnim;
     }
@@ -2495,7 +2510,7 @@
      * @param updated list of shortcuts which have changed.
      */
     @Override
-    public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) {
+    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
         if (!updated.isEmpty()) {
             mWorkspace.updateShortcuts(updated);
         }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 53e5274..bfe327e 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.PredictionModel;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.InstallSessionTracker;
@@ -58,7 +57,6 @@
     private final IconCache mIconCache;
     private final WidgetPreviewLoader mWidgetCache;
     private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private final PredictionModel mPredictionModel;
 
     private SecureSettingsObserver mNotificationDotsObserver;
     private InstallSessionTracker mInstallSessionTracker;
@@ -109,15 +107,11 @@
         mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
                 .registerInstallTracker(mModel, MODEL_EXECUTOR);
 
-        if (!mContext.getResources().getBoolean(R.bool.notification_dots_enabled)) {
-            mNotificationDotsObserver = null;
-        } else {
-            // Register an observer to rebind the notification listener when dots are re-enabled.
-            mNotificationDotsObserver =
-                    newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
-            mNotificationDotsObserver.register();
-            mNotificationDotsObserver.dispatchOnChange();
-        }
+        // Register an observer to rebind the notification listener when dots are re-enabled.
+        mNotificationDotsObserver =
+                newNotificationSettingsObserver(mContext, this::onNotificationSettingsChanged);
+        mNotificationDotsObserver.register();
+        mNotificationDotsObserver.dispatchOnChange();
     }
 
     public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
@@ -128,8 +122,7 @@
         mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
         mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
         mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
-        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
-        mPredictionModel = PredictionModel.newInstance(mContext);
+        mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext));
     }
 
     protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -157,6 +150,7 @@
      * Call from Application.onTerminate(), which is not guaranteed to ever be called.
      */
     public void onTerminate() {
+        mModel.destroy();
         if (mModelChangeReceiver != null) {
             mContext.unregisterReceiver(mModelChangeReceiver);
         }
@@ -185,10 +179,6 @@
         return mModel;
     }
 
-    public PredictionModel getPredictionModel() {
-        return mPredictionModel;
-    }
-
     public WidgetPreviewLoader getWidgetCache() {
         return mWidgetCache;
     }
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ff4b545..8458152 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.config.FeatureFlags.IS_STUDIO_BUILD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.content.Context;
 import android.content.Intent;
@@ -44,8 +43,10 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.CacheDataUpdatedTask;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.PackageInstallStateChangedTask;
 import com.android.launcher3.model.PackageUpdatedTask;
@@ -60,7 +61,6 @@
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 
@@ -85,7 +85,6 @@
 
     private final LauncherAppState mApp;
     private final Object mLock = new Object();
-    private final LooperExecutor mMainExecutor = MAIN_EXECUTOR;
 
     private LoaderTask mLoaderTask;
     private boolean mIsLoaderTaskRunning;
@@ -112,20 +111,22 @@
      */
     private final BgDataModel mBgDataModel = new BgDataModel();
 
+    private final ModelDelegate mModelDelegate;
+
     // Runnable to check if the shortcuts permission has changed.
-    private final Runnable mShortcutPermissionCheckRunnable = new Runnable() {
+    private final Runnable mDataValidationCheck = new Runnable() {
         @Override
         public void run() {
-            if (mModelLoaded && hasShortcutsPermission(mApp.getContext())
-                    != mBgAllAppsList.hasShortcutHostPermission()) {
-                forceReload();
+            if (mModelLoaded) {
+                mModelDelegate.validateData();
             }
         }
     };
 
-    LauncherModel(LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
+    LauncherModel(Context context, LauncherAppState app, IconCache iconCache, AppFilter appFilter) {
         mApp = app;
         mBgAllAppsList = new AllAppsList(iconCache, appFilter);
+        mModelDelegate = ModelDelegate.newInstance(context, app, mBgAllAppsList, mBgDataModel);
     }
 
     /**
@@ -217,6 +218,20 @@
         }
     }
 
+    /**
+     * Called when the workspace items have drastically changed
+     */
+    public void onWorkspaceUiChanged() {
+        MODEL_EXECUTOR.execute(mModelDelegate::workspaceLoadComplete);
+    }
+
+    /**
+     * Called when the model is destroyed
+     */
+    public void destroy() {
+        MODEL_EXECUTOR.execute(mModelDelegate::destroy);
+    }
+
     public void onBroadcastIntent(Intent intent) {
         if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);
         final String action = intent.getAction();
@@ -321,20 +336,21 @@
      */
     public boolean startLoader() {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
-        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
+        ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
         synchronized (mLock) {
             // Don't bother to start the thread if we know it's not going to do anything
             final Callbacks[] callbacksList = getCallbacks();
             if (callbacksList.length > 0) {
                 // Clear any pending bind-runnables from the synchronized load process.
                 for (Callbacks cb : callbacksList) {
-                    mMainExecutor.execute(cb::clearPendingBinds);
+                    MAIN_EXECUTOR.execute(cb::clearPendingBinds);
                 }
 
                 // If there is already one running, tell it to stop.
                 stopLoader();
                 LoaderResults loaderResults = new LoaderResults(
-                        mApp, mBgDataModel, mBgAllAppsList, callbacksList, mMainExecutor);
+                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);
                 if (mModelLoaded && !mIsLoaderTaskRunning) {
                     // Divide the set of loaded items into those that we are binding synchronously,
                     // and everything else that is to be bound normally (asynchronously).
@@ -372,7 +388,8 @@
     public void startLoaderForResults(LoaderResults results) {
         synchronized (mLock) {
             stopLoader();
-            mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
+            mLoaderTask = new LoaderTask(
+                    mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, results);
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
@@ -491,9 +508,9 @@
      * Current implementation simply reloads the workspace, but it can be optimized to
      * use partial updates similar to {@link UserCache}
      */
-    public void refreshShortcutsIfRequired() {
-        MODEL_EXECUTOR.getHandler().removeCallbacks(mShortcutPermissionCheckRunnable);
-        MODEL_EXECUTOR.post(mShortcutPermissionCheckRunnable);
+    public void validateModelDataOnResume() {
+        MODEL_EXECUTOR.getHandler().removeCallbacks(mDataValidationCheck);
+        MODEL_EXECUTOR.post(mDataValidationCheck);
     }
 
     /**
@@ -520,7 +537,7 @@
     }
 
     public void enqueueModelUpdateTask(ModelUpdateTask task) {
-        task.init(mApp, this, mBgDataModel, mBgAllAppsList, mMainExecutor);
+        task.init(mApp, this, mBgDataModel, mBgAllAppsList, MAIN_EXECUTOR);
         MODEL_EXECUTOR.execute(task);
     }
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e8b5568..2973cf7 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -100,10 +100,12 @@
     public static final int SCHEMA_VERSION = 28;
 
     public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
+    public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
 
     static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
 
     protected DatabaseHelper mOpenHelper;
+    protected String mProviderAuthority;
 
     private long mLastRestoreTimestamp = 0L;
 
@@ -367,7 +369,8 @@
             case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
                 Bundle result = new Bundle();
                 result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                        Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false));
+                        Utilities.getPrefs(getContext()).getBoolean(
+                                mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
                 return result;
             }
             case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
@@ -437,6 +440,7 @@
                                             getContext(), true /* forMigration */)));
                     return result;
                 }
+                return null;
             }
             case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
                 if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
@@ -450,6 +454,23 @@
                                     () -> mOpenHelper));
                     return result;
                 }
+                return null;
+            }
+            case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
+                if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
+                final DatabaseHelper helper = mOpenHelper;
+                if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
+                    mProviderAuthority = null;
+                } else {
+                    mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
+                }
+                mOpenHelper = DatabaseHelper.createDatabaseHelper(
+                        getContext(), arg, false /* forMigration */);
+                helper.close();
+                LauncherAppState app = LauncherAppState.getInstanceNoCreate();
+                if (app == null) return null;
+                app.getModel().forceReload();
+                return null;
             }
         }
         return null;
@@ -492,7 +513,8 @@
     }
 
     private void clearFlagEmptyDbCreated() {
-        Utilities.getPrefs(getContext()).edit().remove(EMPTY_DATABASE_CREATED).commit();
+        Utilities.getPrefs(getContext()).edit()
+                .remove(mOpenHelper.getKey(EMPTY_DATABASE_CREATED)).commit();
     }
 
     /**
@@ -505,7 +527,7 @@
     synchronized private void loadDefaultFavoritesIfNecessary() {
         SharedPreferences sp = Utilities.getPrefs(getContext());
 
-        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
+        if (sp.getBoolean(mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false)) {
             Log.d(TAG, "loading default workspace");
 
             AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
@@ -553,8 +575,13 @@
      */
     private AutoInstallsLayout createWorkspaceLoaderFromAppRestriction(AppWidgetHost widgetHost) {
         Context ctx = getContext();
-        String authority = Settings.Secure.getString(ctx.getContentResolver(),
-                "launcher3.layout.provider");
+        final String authority;
+        if (!TextUtils.isEmpty(mProviderAuthority)) {
+            authority = mProviderAuthority;
+        } else {
+            authority = Settings.Secure.getString(ctx.getContentResolver(),
+                    "launcher3.layout.provider");
+        }
         if (TextUtils.isEmpty(authority)) {
             return null;
         }
@@ -694,11 +721,25 @@
         }
 
         /**
+         * Re-composite given key in respect to database. If the current db is
+         * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+         * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+         * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+         */
+        String getKey(final String key) {
+            if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
+                return key;
+            }
+            return key + "@" + getDatabaseName();
+        }
+
+        /**
          * Overriden in tests.
          */
         protected void onEmptyDbCreated() {
             // Set the flag for empty DB
-            Utilities.getPrefs(mContext).edit().putBoolean(EMPTY_DATABASE_CREATED, true).commit();
+            Utilities.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
+                    .commit();
         }
 
         public long getSerialNumberForUser(UserHandle user) {
@@ -872,7 +913,6 @@
          * Removes widgets which are registered to the Launcher's host, but are not present
          * in our model.
          */
-        @TargetApi(Build.VERSION_CODES.O)
         public void removeGhostWidgets(SQLiteDatabase db) {
             // Get all existing widget ids.
             final AppWidgetHost host = newLauncherWidgetHost();
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 5512654..58a418e 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -354,14 +354,20 @@
 
         public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
 
+        public static final String METHOD_SWITCH_DATABASE = "switch_database";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
-            return call(cr, method, null);
+            return call(cr, method, null /* arg */);
         }
 
         public static Bundle call(ContentResolver cr, String method, String arg) {
-            return cr.call(CONTENT_URI, method, arg, null);
+            return call(cr, method, arg, null /* extras */);
+        }
+
+        public static Bundle call(ContentResolver cr, String method, String arg, Bundle extras) {
+            return cr.call(CONTENT_URI, method, arg, extras);
         }
     }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 39b0f2f..b6bc500 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
-import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
@@ -117,8 +116,6 @@
     public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
 
     public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
-    public static final LauncherState OVERVIEW_PEEK =
-            OverviewState.newPeekState(OVERVIEW_PEEK_STATE_ORDINAL);
     public static final LauncherState OVERVIEW_MODAL_TASK = OverviewState.newModalTaskState(
             OVERVIEW_MODAL_TASK_STATE_ORDINAL);
     public static final LauncherState QUICK_SWITCH =
diff --git a/src/com/android/launcher3/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 5f6ecb5..f2a3de7 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -38,7 +38,6 @@
     protected void init(Context context) {
         FileLog.setDir(context.getApplicationContext().getFilesDir());
         FeatureFlags.initialize(context);
-        SessionCommitReceiver.applyDefaultUserPrefs(context);
         IconShape.init(context);
 
         if (BitmapCreationCheck.ENABLED) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e29faac..4303dee 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -195,9 +195,7 @@
         mMinFlingVelocity = (int) (MIN_FLING_VELOCITY * density);
         mMinSnapVelocity = (int) (MIN_SNAP_VELOCITY * density);
 
-        if (Utilities.ATLEAST_OREO) {
-            setDefaultFocusHighlightEnabled(false);
-        }
+        setDefaultFocusHighlightEnabled(false);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -1448,11 +1446,8 @@
         int minDistanceFromScreenCenterIndex = -1;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
-            View layout = getPageAt(i);
-            int childSize = mOrientationHandler.getMeasuredSize(layout);
-            int halfChildSize = (childSize / 2);
-            int childCenter = getChildOffset(i) + halfChildSize;
-            int distanceFromScreenCenter = Math.abs(childCenter - screenCenter);
+            int distanceFromScreenCenter = Math.abs(
+                    getDisplacementFromScreenCenter(i, screenCenter));
             if (distanceFromScreenCenter < minDistanceFromScreenCenter) {
                 minDistanceFromScreenCenter = distanceFromScreenCenter;
                 minDistanceFromScreenCenterIndex = i;
@@ -1461,6 +1456,20 @@
         return minDistanceFromScreenCenterIndex;
     }
 
+    private int getDisplacementFromScreenCenter(int childIndex, int screenCenter) {
+        View layout = getPageAt(childIndex);
+        int childSize = mOrientationHandler.getMeasuredSize(layout);
+        int halfChildSize = (childSize / 2);
+        int childCenter = getChildOffset(childIndex) + halfChildSize;
+        return childCenter - screenCenter;
+    }
+
+    protected int getDisplacementFromScreenCenter(int childIndex) {
+        int pageOrientationSize = mOrientationHandler.getMeasuredSize(this);
+        int screenCenter = mOrientationHandler.getPrimaryScroll(this) + (pageOrientationSize / 2);
+        return getDisplacementFromScreenCenter(childIndex, screenCenter);
+    }
+
     protected void snapToDestination() {
         snapToPage(getPageNearestToCenterOfScreen(), getPageSnapDuration());
     }
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 56875bb..2df7f5a 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -49,7 +49,6 @@
 import com.android.launcher3.util.Themes;
 
 import java.net.URISyntaxException;
-import java.util.ArrayList;
 
 /**
  * Drop target which provides a secondary option for an item.
@@ -340,12 +339,6 @@
         }
 
         @Override
-        public void fillInLogContainerData(ItemInfo childInfo, Target child,
-                ArrayList<Target> parents) {
-            mOriginal.fillInLogContainerData(childInfo, child, parents);
-        }
-
-        @Override
         public void onLauncherResume() {
             // We use MATCH_UNINSTALLED_PACKAGES as the app can be on SD card as well.
             if (new PackageManagerHelper(mContext).getApplicationInfo(mPackageName,
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 89f0a3d..007e5f5 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -16,55 +16,29 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
-
-import android.annotation.TargetApi;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.LauncherActivityInfo;
-import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.Build;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.Log;
 
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.util.Executors;
-
-import java.util.List;
 
 /**
  * BroadcastReceiver to handle session commit intent.
  */
-@TargetApi(Build.VERSION_CODES.O)
 public class SessionCommitReceiver extends BroadcastReceiver {
 
-    private static final String TAG = "SessionCommitReceiver";
-
-    // The content provider for the add to home screen setting. It should be of the format:
-    // <package name>.addtohomescreen
-    private static final String MARKER_PROVIDER_PREFIX = ".addtohomescreen";
-
     // Preference key for automatically adding icon to homescreen.
     public static final String ADD_ICON_PREFERENCE_KEY = "pref_add_icon_to_home";
-    public static final String ADD_ICON_PREFERENCE_INITIALIZED_KEY =
-            "pref_add_icon_to_home_initialized";
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!isEnabled(context) || !Utilities.ATLEAST_OREO) {
+        if (!isEnabled(context)) {
             // User has decided to not add icons on homescreen.
             return;
         }
@@ -86,105 +60,11 @@
             return;
         }
 
-        queueAppIconAddition(context, info.getAppPackageName(), user);
-    }
-
-    public static void queuePromiseAppIconAddition(Context context, SessionInfo sessionInfo) {
-        String packageName = sessionInfo.getAppPackageName();
-        if (context.getSystemService(LauncherApps.class)
-                .getActivityList(packageName, getUserHandle(sessionInfo)).isEmpty()) {
-            // Ensure application isn't already installed.
-            queueAppIconAddition(context, packageName, sessionInfo.getAppLabel(),
-                    sessionInfo.getAppIcon(), getUserHandle(sessionInfo));
-        }
-    }
-
-    public static void queueAppIconAddition(Context context, String packageName, UserHandle user) {
-        List<LauncherActivityInfo> activities = context.getSystemService(LauncherApps.class)
-                .getActivityList(packageName, user);
-        if (activities.isEmpty()) {
-            // no activity found
-            return;
-        }
-        queueAppIconAddition(context, packageName, activities.get(0).getLabel(), null, user);
-    }
-
-    private static void queueAppIconAddition(Context context, String packageName,
-            CharSequence label, Bitmap icon, UserHandle user) {
-        Intent data = new Intent();
-        data.putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent().setComponent(
-                new ComponentName(packageName, "")).setPackage(packageName));
-        data.putExtra(Intent.EXTRA_SHORTCUT_NAME, label);
-        data.putExtra(Intent.EXTRA_SHORTCUT_ICON, icon);
-
-        InstallShortcutReceiver.queueApplication(data, user, context);
+        ItemInstallQueue.INSTANCE.get(context)
+                .queueItem(info.getAppPackageName(), user);
     }
 
     public static boolean isEnabled(Context context) {
         return Utilities.getPrefs(context).getBoolean(ADD_ICON_PREFERENCE_KEY, true);
     }
-
-    public static void applyDefaultUserPrefs(final Context context) {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        if (prefs.getAll().isEmpty()) {
-            // This logic assumes that the code is the first thing that is executed (before any
-            // shared preference is written).
-            // TODO: Move this logic to DB upgrade once we have proper support for db downgrade
-            // If it is a fresh start, just apply the default value. We use prefs.isEmpty() to infer
-            // a fresh start as put preferences always contain some values corresponding to current
-            // grid.
-            prefs.edit().putBoolean(ADD_ICON_PREFERENCE_KEY, true).apply();
-        } else if (!prefs.contains(ADD_ICON_PREFERENCE_INITIALIZED_KEY)) {
-            new PrefInitTask(context).executeOnExecutor(Executors.THREAD_POOL_EXECUTOR);
-        }
-    }
-
-    private static class PrefInitTask extends AsyncTask<Void, Void, Void> {
-        private final Context mContext;
-
-        PrefInitTask(Context context) {
-            mContext = context;
-        }
-
-        @Override
-        protected Void doInBackground(Void... voids) {
-            boolean addIconToHomeScreenEnabled = readValueFromMarketApp();
-            Utilities.getPrefs(mContext).edit()
-                    .putBoolean(ADD_ICON_PREFERENCE_KEY, addIconToHomeScreenEnabled)
-                    .putBoolean(ADD_ICON_PREFERENCE_INITIALIZED_KEY, true)
-                    .apply();
-            return null;
-        }
-
-        public boolean readValueFromMarketApp() {
-            // Get the marget package
-            ResolveInfo ri = mContext.getPackageManager().resolveActivity(
-                    new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_APP_MARKET),
-                    PackageManager.MATCH_DEFAULT_ONLY | PackageManager.MATCH_SYSTEM_ONLY);
-            if (ri == null) {
-                return true;
-            }
-
-            Cursor c = null;
-            try {
-                c = mContext.getContentResolver().query(
-                        Uri.parse("content://" + ri.activityInfo.packageName
-                                + MARKER_PROVIDER_PREFIX),
-                        null, null, null, null);
-                if (c.moveToNext()) {
-                    return c.getInt(c.getColumnIndexOrThrow(Settings.NameValueTable.VALUE)) != 0;
-                }
-            } catch (Exception e) {
-                Log.d(TAG, "Error reading add to homescreen preference", e);
-            } finally {
-                if (c != null) {
-                    c.close();
-                }
-            }
-            return true;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index bf63788..dea2a8d 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -18,7 +18,6 @@
 
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
 
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.app.Person;
@@ -26,6 +25,7 @@
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
@@ -48,7 +48,6 @@
 import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.TransactionTooLargeException;
 import android.provider.Settings;
 import android.text.Spannable;
@@ -65,7 +64,6 @@
 
 import androidx.core.os.BuildCompat;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.GridOptionsProvider;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -113,12 +111,6 @@
     public static final boolean ATLEAST_P =
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
 
-    public static final boolean ATLEAST_OREO_MR1 =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1;
-
-    public static final boolean ATLEAST_OREO =
-            Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
-
     /**
      * Set on a motion event dispatched from the nav bar. See {@link MotionEvent#setEdgeFlags(int)}.
      */
@@ -494,21 +486,6 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
-    /**
-     * @return {@link SharedPreferences} that backs {@link FeatureFlags}
-     */
-    public static SharedPreferences getFeatureFlagsPrefs(Context context) {
-        // Use application context for shared preferences, so that we use a single cached instance
-        return context.getApplicationContext().getSharedPreferences(
-            FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
-    }
-
-    public static boolean areAnimationsEnabled(Context context) {
-        return ATLEAST_OREO
-                ? ValueAnimator.areAnimatorsEnabled()
-                : !context.getSystemService(PowerManager.class).isPowerSaveMode();
-    }
-
     public static boolean isWallpaperAllowed(Context context) {
         return context.getSystemService(WallpaperManager.class).isSetWallpaperAllowed();
     }
@@ -663,6 +640,14 @@
         }
     }
 
+    /**
+     * @return true is the extra is either null or is of type {@param type}
+     */
+    public static boolean isValidExtraType(Intent intent, String key, Class type) {
+        Object extra = intent.getParcelableExtra(key);
+        return extra == null || type.isInstance(extra);
+    }
+
     public static float squaredHypot(float x, float y) {
         return x * x + y * y;
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index a6283ff..45aaa1b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -22,12 +22,12 @@
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED;
 import static com.android.launcher3.LauncherState.FLAG_WORKSPACE_INACCESSIBLE;
+import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPERIGHT;
@@ -93,12 +93,12 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -115,6 +115,7 @@
 
 import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.function.Predicate;
 
 /**
@@ -436,10 +437,6 @@
             enforceDragParity("onDragEnd", 0, 0);
         }
 
-        if (!mDeferRemoveExtraEmptyScreen) {
-            removeExtraEmptyScreen(mDragSourceInternal != null);
-        }
-
         updateChildrenLayersEnabled();
         mDragInfo = null;
         mOutlineProvider = null;
@@ -657,6 +654,7 @@
         convertFinalScreenToEmptyScreenIfNecessary();
         if (hasExtraEmptyScreen()) {
             removeView(mWorkspaceScreens.get(EXTRA_EMPTY_SCREEN_ID));
+            setCurrentPage(getNextPage());
             mWorkspaceScreens.remove(EXTRA_EMPTY_SCREEN_ID);
             mScreenOrder.removeValue(EXTRA_EMPTY_SCREEN_ID);
 
@@ -942,7 +940,10 @@
         super.onScrollChanged(l, t, oldl, oldt);
 
         // Update the page indicator progress.
-        boolean isTransitioning = mIsSwitchingState
+        // Unlike from other states, we show the page indicator when transitioning from HINT_STATE.
+        boolean isSwitchingState = mIsSwitchingState
+                && mLauncher.getStateManager().getCurrentStableState() != HINT_STATE;
+        boolean isTransitioning = isSwitchingState
                 || (getLayoutTransition() != null && getLayoutTransition().isRunning());
         if (!isTransitioning) {
             showPageIndicatorAtCurrentScroll();
@@ -1501,7 +1502,6 @@
                     .showForIcon((BubbleTextView) child);
             if (popupContainer != null) {
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition();
-                mLauncher.getUserEventDispatcher().resetElapsedContainerMillis("dragging started");
             }
         }
 
@@ -1701,7 +1701,6 @@
                 fi.addItem(destInfo);
                 fi.addItem(sourceInfo);
             }
-            mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
             return true;
         }
         return false;
@@ -1718,7 +1717,7 @@
         if (dropOverView instanceof FolderIcon) {
             FolderIcon fi = (FolderIcon) dropOverView;
             if (fi.acceptDrop(d.dragInfo)) {
-                mStatsLogManager.logger().withItemInfo(fi.mInfo).withInstanceId(d.logInstanceId)
+                mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                         .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
                 fi.onDrop(d, false /* itemReturnedOnFailedDrop */);
 
@@ -1872,6 +1871,18 @@
                             };
                         }
                     }
+                    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);
@@ -2452,7 +2463,7 @@
             Runnable onAnimationCompleteRunnable = new Runnable() {
                 @Override
                 public void run() {
-                    // Normally removeExtraEmptyScreen is called in Workspace#onDragEnd, but when
+                    // Normally removeExtraEmptyScreen is called in Workspace#onDrop, but when
                     // adding an item that may not be dropped right away (due to a config activity)
                     // we defer the removal until the activity returns.
                     deferRemoveExtraEmptyScreen();
@@ -3087,7 +3098,7 @@
         return false;
     }
 
-    void updateShortcuts(ArrayList<WorkspaceItemInfo> shortcuts) {
+    void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
         final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
         ItemOperator op = (info, v) -> {
             if (v instanceof BubbleTextView && updates.contains(info)) {
@@ -3262,29 +3273,11 @@
         }
         if (nScreens == 0) {
             // When the workspace is not loaded, we do not know how many screen will be bound.
-            return getContext().getString(R.string.all_apps_home_button_label);
+            return getContext().getString(R.string.home_screen);
         }
         return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
-                || childInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
-            getHotseat().fillInLogContainerData(childInfo, child, parents);
-            return;
-        } else if (childInfo.container >= 0) {
-            FolderIcon icon = (FolderIcon) getHomescreenIconByItemId(childInfo.container);
-            icon.getFolder().fillInLogContainerData(childInfo, child, parents);
-            return;
-        }
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = getCurrentPage();
-        parents.add(newContainerTarget(ContainerType.WORKSPACE));
-    }
-
     /**
      * Used as a workaround to ensure that the AppWidgetService receives the
      * PACKAGE_ADDED broadcast before updating widgets.
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index c3d4aeb..ea887cc 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -28,7 +28,7 @@
 
     String TAG = "Launcher.Workspace";
 
-    // The screen id used for the empty screen always present to the right.
+    // The screen id used for the empty screen always present at the end.
     int EXTRA_EMPTY_SCREEN_ID = -201;
     // The is the first screen. It is always present, even if its empty.
     int FIRST_SCREEN_ID = 0;
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 77b8a32..5079469 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.allapps;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -42,6 +43,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.StringRes;
+import androidx.core.os.BuildCompat;
 import androidx.recyclerview.widget.DefaultItemAnimator;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
@@ -55,19 +57,19 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
 
-import java.util.ArrayList;
+import java.util.function.IntConsumer;
 
 /**
  * The all apps view container.
@@ -107,7 +109,7 @@
 
     private final MultiValueAlpha mMultiValueAlpha;
 
-    Rect mInsets = new Rect();
+    private Rect mInsets = new Rect();
 
     public AllAppsContainerView(Context context) {
         this(context, null);
@@ -203,6 +205,17 @@
         mAH[AdapterHolder.WORK].applyPadding();
     }
 
+    private void hideInput() {
+        if (!BuildCompat.isAtLeastR() || !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
+
+        WindowInsets insets = getRootWindowInsets();
+        if (insets == null) return;
+
+        if (insets.isVisible(WindowInsets.Type.ime())) {
+            getWindowInsetsController().hide(WindowInsets.Type.ime());
+        }
+    }
+
     /**
      * Returns whether the view itself will handle the touch event or not.
      */
@@ -218,9 +231,14 @@
         }
         if (rv.getScrollbar().getThumbOffsetY() >= 0 &&
                 mLauncher.getDragLayer().isEventOverView(rv.getScrollbar(), ev)) {
+            hideInput();
             return false;
         }
-        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
+        if (shouldScroll) {
+            hideInput();
+        }
+        return shouldScroll;
     }
 
     @Override
@@ -335,13 +353,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        parents.add(newContainerTarget(
-                getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
-    }
-
-    @Override
     public void setInsets(Rect insets) {
         mInsets.set(insets);
         DeviceProfile grid = mLauncher.getDeviceProfile();
@@ -486,7 +497,7 @@
         if (mWorkModeSwitch != null) {
             mWorkModeSwitch.setWorkTabVisible(pos == AdapterHolder.WORK
                     && mAllAppsStore.hasModelFlag(
-                            FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
+                    FLAG_HAS_SHORTCUT_PERMISSION | FLAG_QUIET_MODE_CHANGE_PERMISSION));
         }
     }
 
@@ -526,6 +537,42 @@
         return mViewPager == null ? getActiveRecyclerView() : mViewPager;
     }
 
+    /**
+     * Handles selection on focused view and returns success
+     */
+    public boolean selectFocusedView(View v) {
+        ItemInfo headerItem = getHighlightedItemFromHeader();
+        if (headerItem != null) {
+            return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
+        }
+        AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
+        if (focusedItem instanceof AdapterItemWithPayload) {
+            IntConsumer onSelection =
+                    ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
+            if (onSelection != null) {
+                onSelection.accept(SearchTargetEvent.QUICK_SELECT);
+                return true;
+            }
+        }
+        if (focusedItem.appInfo != null) {
+            ItemInfo itemInfo = focusedItem.appInfo;
+            return mLauncher.startActivitySafely(v, itemInfo.getIntent(), itemInfo);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the ItemInfo of a focused view inside {@link FloatingHeaderView}
+     */
+    public ItemInfo getHighlightedItemFromHeader() {
+        View view = getFloatingHeaderView().getFocusedChild();
+        if (view != null && view.getTag() instanceof ItemInfo) {
+            return ((ItemInfo) view.getTag());
+        }
+
+        return null;
+    }
+
     public RecyclerViewFastScroller getScrollBar() {
         AllAppsRecyclerView rv = getActiveRecyclerView();
         return rv == null ? null : rv.getScrollbar();
@@ -538,6 +585,10 @@
         int padding = mHeader.getMaxTranslation();
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.top = padding;
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) {
+                //add extra space between tabs and recycler view
+                mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx;
+            }
             mAH[i].applyPadding();
         }
     }
@@ -652,6 +703,10 @@
             applyVerticalFadingEdgeEnabled(verticalFadingEdge);
             applyPadding();
             setupOverlay();
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                recyclerView.addItemDecoration(new AllAppsSectionDecorator(
+                        AllAppsContainerView.this));
+            }
         }
 
         void setupOverlay() {
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index d653160..d1340fa 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -20,6 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -30,26 +32,40 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.view.accessibility.AccessibilityEventCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
+import androidx.lifecycle.LiveData;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
+import androidx.slice.Slice;
+import androidx.slice.widget.SliceLiveData;
+import androidx.slice.widget.SliceView;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.views.HeroSearchResultView;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.List;
+import java.util.function.IntConsumer;
 
 /**
  * The grid view adapter of all the apps.
  */
-public class AllAppsGridAdapter extends RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
+public class AllAppsGridAdapter extends
+        RecyclerView.Adapter<AllAppsGridAdapter.ViewHolder> {
 
     public static final String TAG = "AppsGridAdapter";
 
@@ -66,6 +82,20 @@
     // A divider that separates the apps list and the search market button
     public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
 
+    public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
+
+    public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
+
+    public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7;
+
+    public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8;
+
+    public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 9;
+
+    public static final int VIEW_TYPE_SEARCH_SHORTCUT = 1 << 10;
+
+    public static final int VIEW_TYPE_SEARCH_PEOPLE = 1 << 11;
+
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON;
@@ -81,6 +111,123 @@
     }
 
     /**
+     * Info about a particular adapter item (can be either section or app)
+     */
+    public static class AdapterItem {
+        /** Common properties */
+        // The index of this adapter item in the list
+        public int position;
+        // The type of this item
+        public int viewType;
+
+        /** App-only properties */
+        // The section name of this app.  Note that there can be multiple items with different
+        // sectionNames in the same section
+        public String sectionName = null;
+        // The row that this item shows up on
+        public int rowIndex;
+        // The index of this app in the row
+        public int rowAppIndex;
+        // The associated AppInfo for the app
+        public AppInfo appInfo = null;
+        // The index of this app not including sections
+        public int appIndex = -1;
+        // Search section associated to result
+        public SearchSectionInfo searchSectionInfo = null;
+
+        /**
+         * Factory method for AppIcon AdapterItem
+         */
+        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
+                int appIndex) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ICON;
+            item.position = pos;
+            item.sectionName = sectionName;
+            item.appInfo = appInfo;
+            item.appIndex = appIndex;
+            return item;
+        }
+
+        /**
+         * Factory method for empty search results view
+         */
+        public static AdapterItem asEmptySearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_EMPTY_SEARCH;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a dividerView in AllAppsSearch
+         */
+        public static AdapterItem asAllAppsDivider(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_ALL_APPS_DIVIDER;
+            item.position = pos;
+            return item;
+        }
+
+        /**
+         * Factory method for a market search button
+         */
+        public static AdapterItem asMarketSearch(int pos) {
+            AdapterItem item = new AdapterItem();
+            item.viewType = VIEW_TYPE_SEARCH_MARKET;
+            item.position = pos;
+            return item;
+        }
+
+        boolean isCountedForAccessibility() {
+            return viewType == VIEW_TYPE_ICON
+                    || viewType == VIEW_TYPE_SEARCH_HERO_APP
+                    || viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
+                    || viewType == VIEW_TYPE_SEARCH_SLICE
+                    || viewType == VIEW_TYPE_SEARCH_ROW
+                    || viewType == VIEW_TYPE_SEARCH_PEOPLE
+                    || viewType == VIEW_TYPE_SEARCH_SHORTCUT;
+        }
+    }
+
+    /**
+     * Extension of AdapterItem that contains an extra payload specific to item
+     *
+     * @param <T> Play load Type
+     */
+    public static class AdapterItemWithPayload<T> extends AdapterItem {
+        private T mPayload;
+        private AllAppsSearchPlugin mPlugin;
+        private IntConsumer mSelectionHandler;
+
+        public AllAppsSearchPlugin getPlugin() {
+            return mPlugin;
+        }
+
+        public void setPlugin(AllAppsSearchPlugin plugin) {
+            mPlugin = plugin;
+        }
+
+        public AdapterItemWithPayload(T payload, int type, AllAppsSearchPlugin plugin) {
+            mPayload = payload;
+            viewType = type;
+            mPlugin = plugin;
+        }
+
+        public void setSelectionHandler(IntConsumer runnable) {
+            mSelectionHandler = runnable;
+        }
+
+        public IntConsumer getSelectionHandler() {
+            return mSelectionHandler;
+        }
+
+        public T getPayload() {
+            return mPayload;
+        }
+    }
+
+    /**
      * A subclass of GridLayoutManager that overrides accessibility values during app search.
      */
     public class AppsGridLayoutManager extends GridLayoutManager {
@@ -254,10 +401,12 @@
             case VIEW_TYPE_ICON:
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         R.layout.all_apps_icon, parent, false);
-                icon.setOnClickListener(mOnIconClickListener);
-                icon.setOnLongClickListener(mOnIconLongClickListener);
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mIconFocusListener);
+                if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                    icon.setOnClickListener(mOnIconClickListener);
+                    icon.setOnLongClickListener(mOnIconLongClickListener);
+                }
 
                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
                 icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
@@ -274,6 +423,27 @@
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
+            case VIEW_TYPE_SEARCH_CORPUS_TITLE:
+                return new ViewHolder(
+                        mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
+            case VIEW_TYPE_SEARCH_HERO_APP:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_hero_app, parent, false));
+            case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_play_item, parent, false));
+            case VIEW_TYPE_SEARCH_ROW:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_settings_row, parent, false));
+            case VIEW_TYPE_SEARCH_SLICE:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_slice, parent, false));
+            case VIEW_TYPE_SEARCH_SHORTCUT:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_shortcut, parent, false));
+            case VIEW_TYPE_SEARCH_PEOPLE:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_people_item, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -283,10 +453,36 @@
     public void onBindViewHolder(ViewHolder holder, int position) {
         switch (holder.getItemViewType()) {
             case VIEW_TYPE_ICON:
-                AppInfo info = mApps.getAdapterItems().get(position).appInfo;
+                AdapterItem adapterItem = mApps.getAdapterItems().get(position);
+                AppInfo info = adapterItem.appInfo;
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
                 icon.applyFromApplicationInfo(info);
+                if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                    break;
+                }
+                //TODO: replace with custom TopHitBubbleTextView with support for both shortcut
+                // and apps
+                if (adapterItem instanceof AdapterItemWithPayload) {
+                    AdapterItemWithPayload withPayload = (AdapterItemWithPayload) adapterItem;
+                    IntConsumer selectionHandler = type -> {
+                        SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
+                                type);
+                        e.bundle = HeroSearchResultView.getAppBundle(info);
+                        if (withPayload.getPlugin() != null) {
+                            withPayload.getPlugin().notifySearchTargetEvent(e);
+                        }
+                    };
+                    icon.setOnClickListener(view -> {
+                        selectionHandler.accept(SearchTargetEvent.SELECT);
+                        mOnIconClickListener.onClick(view);
+                    });
+                    icon.setOnLongClickListener(view -> {
+                        selectionHandler.accept(SearchTargetEvent.LONG_PRESS);
+                        return mOnIconLongClickListener.onLongClick(view);
+                    });
+                    withPayload.setSelectionHandler(selectionHandler);
+                }
                 break;
             case VIEW_TYPE_EMPTY_SEARCH:
                 TextView emptyViewText = (TextView) holder.itemView;
@@ -302,6 +498,37 @@
                     searchView.setVisibility(View.GONE);
                 }
                 break;
+            case VIEW_TYPE_SEARCH_SLICE:
+                SliceView sliceView = (SliceView) holder.itemView;
+                AdapterItemWithPayload<Uri> item =
+                        (AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
+                sliceView.setOnSliceActionListener((info1, s) -> {
+                    if (item.getPlugin() != null) {
+                        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                                SearchTarget.ItemType.SETTINGS_SLICE,
+                                SearchTargetEvent.CHILD_SELECT);
+                        searchTargetEvent.bundle = new Bundle();
+                        searchTargetEvent.bundle.putParcelable("uri", item.getPayload());
+                        item.getPlugin().notifySearchTargetEvent(searchTargetEvent);
+                    }
+                });
+                try {
+                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher, item.getPayload());
+                    liveData.observe((Launcher) mLauncher, sliceView);
+                    sliceView.setTag(liveData);
+                } catch (Exception ignored) {
+                }
+                break;
+            case VIEW_TYPE_SEARCH_CORPUS_TITLE:
+            case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
+            case VIEW_TYPE_SEARCH_HERO_APP:
+            case VIEW_TYPE_SEARCH_ROW:
+            case VIEW_TYPE_SEARCH_SHORTCUT:
+            case VIEW_TYPE_SEARCH_PEOPLE:
+                PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
+                payloadResultView.applyAdapterInfo(
+                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position));
+                break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
                 break;
@@ -309,6 +536,25 @@
     }
 
     @Override
+    public void onViewRecycled(@NonNull ViewHolder holder) {
+        super.onViewRecycled(holder);
+        if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
+        if (holder.itemView instanceof BubbleTextView) {
+            BubbleTextView icon = (BubbleTextView) holder.itemView;
+            icon.setOnClickListener(mOnIconClickListener);
+            icon.setOnLongClickListener(mOnIconLongClickListener);
+        } else if (holder.itemView instanceof SliceView) {
+            SliceView sliceView = (SliceView) holder.itemView;
+            sliceView.setOnSliceActionListener(null);
+            if (sliceView.getTag() instanceof LiveData) {
+                LiveData sliceLiveData = (LiveData) sliceView.getTag();
+                sliceLiveData.removeObservers((Launcher) mLauncher);
+            }
+        }
+    }
+
+
+    @Override
     public boolean onFailedToRecycleView(ViewHolder holder) {
         // Always recycle and we will reset the view when it is bound
         return true;
@@ -321,8 +567,7 @@
 
     @Override
     public int getItemViewType(int position) {
-        AlphabeticalAppsList.AdapterItem item = mApps.getAdapterItems().get(position);
+        AdapterItem item = mApps.getAdapterItems().get(position);
         return item.viewType;
     }
-
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
new file mode 100644
index 0000000..d7af5f1
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -0,0 +1,197 @@
+/*
+ * 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.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 androidx.annotation.RequiresApi;
+import androidx.core.os.BuildCompat;
+
+/**
+ * Handles IME over all apps to be synchronously transitioning along with the passed in
+ * root inset.
+ */
+public class AllAppsInsetTransitionController {
+
+    private static final boolean DEBUG = false;
+    private static final String TAG = "AllAppsInsetTransitionController";
+    private static final Interpolator LINEAR = new LinearInterpolator();
+
+    private WindowInsetsAnimationController mAnimationController;
+    private WindowInsetsAnimationControlListener mCurrentRequest;
+
+    private float mAllAppsHeight;
+
+    private int mDownInsetBottom;
+    private boolean mShownAtDown;
+
+    private int mHiddenInsetBottom;
+    private int mShownInsetBottom;
+
+    private float mDown, mCurrent;
+    private View mApps;
+
+    public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
+        mAllAppsHeight = allAppsHeight;
+        mApps = appsView;
+    }
+
+    public void hide() {
+        if (!BuildCompat.isAtLeastR()) return;
+
+        WindowInsets insets = mApps.getRootWindowInsets();
+        if (insets == null) return;
+
+        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
+     */
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    public void onDragStart(float progress) {
+        if (!BuildCompat.isAtLeastR()) return;
+        onAnimationEnd(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());
+
+        // override this value based on what it should actually be.
+        mShownAtDown = Float.compare(progress, 1f) == 0 ? false : true;
+        mDownInsetBottom = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+        if (DEBUG) {
+            Log.d(TAG, "\nonDragStart 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 (mCurrentRequest == this) {
+                            mAnimationController = controller;
+                        } else {
+                            controller.finish(mShownAtDown);
+                        }
+                    }
+
+                    @Override
+                    public void onFinished(WindowInsetsAnimationController controller) {
+                        // when screen lock happens, then this method get called
+                        mAnimationController.finish(false);
+                        mAnimationController = null;
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onFinished ctrl=" + controller);
+                        }
+                    }
+
+                    @Override
+                    public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+                        mAnimationController = null;
+                        if (controller != null) {
+                            controller.finish(mShownAtDown);
+                        }
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onCancelled ctrl=" + controller);
+                        }
+                    }
+                });
+    }
+
+    /**
+     * Handles the translation using the progress.
+     *
+     * @param progress value between 0..1
+     */
+    @RequiresApi(api = 30)
+    public void setProgress(float progress) {
+        if (!BuildCompat.isAtLeastR()) 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);
+
+        if (DEBUG) {
+            Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
+                    + mDown + " hidden=" + mHiddenInsetBottom
+                    + " shown=" + mShownInsetBottom
+                    + " mDownInsets.bottom=" + mDownInsetBottom + " inset:" + inset
+                    + " shift: " + shift);
+        }
+        final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
+        final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
+        inset = Math.max(inset, mHiddenInsetBottom);
+        inset = Math.min(inset, mShownInsetBottom);
+        Log.d(TAG, "updateInset inset:" + inset);
+
+        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
+     */
+    @RequiresApi(api = 30)
+    public void onAnimationEnd(float progress) {
+        if (DEBUG) {
+            Log.d(TAG, "endTranslation progress=" + progress
+                    + " mAnimationController=" + mAnimationController);
+        }
+
+        if (mAnimationController == null) return;
+
+        if (Float.compare(progress, 1f) == 0 /* bottom */) {
+            mAnimationController.finish(false /* gone */);
+        }
+        if (Float.compare(progress, 0f) == 0 /* top */) {
+            mAnimationController.finish(true /* show */);
+        }
+        mAnimationController = null;
+        mCurrentRequest = null;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index cbf02b7..13a93ff 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -107,6 +107,7 @@
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET, 1);
         pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_ICON, approxRows * mNumAppsPerRow);
+        pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_SEARCH_CORPUS_TITLE, 1);
 
         mViewHeights.clear();
         mViewHeights.put(AllAppsGridAdapter.VIEW_TYPE_ICON, grid.allAppsCellHeightPx);
@@ -277,7 +278,7 @@
         if (mApps == null) {
             return;
         }
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
 
         // Skip early if there are no items or we haven't been measured
         if (items.isEmpty() || mNumAppsPerRow == 0) {
@@ -351,7 +352,7 @@
     @Override
     public int getCurrentScrollY() {
         // Return early if there are no items or we haven't been measured
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
         if (items.isEmpty() || mNumAppsPerRow == 0 || getChildCount() == 0) {
             return -1;
         }
@@ -367,14 +368,14 @@
     }
 
     public int getCurrentScrollY(int position, int offset) {
-        List<AlphabeticalAppsList.AdapterItem> items = mApps.getAdapterItems();
-        AlphabeticalAppsList.AdapterItem posItem = position < items.size() ?
-                items.get(position) : null;
+        List<AllAppsGridAdapter.AdapterItem> items = mApps.getAdapterItems();
+        AllAppsGridAdapter.AdapterItem posItem = position < items.size()
+                ? items.get(position) : null;
         int y = mCachedScrollPositions.get(position, -1);
         if (y < 0) {
             y = 0;
             for (int i = 0; i < position; i++) {
-                AlphabeticalAppsList.AdapterItem item = items.get(i);
+                AllAppsGridAdapter.AdapterItem item = items.get(i);
                 if (AllAppsGridAdapter.isIconViewType(item.viewType)) {
                     // Break once we reach the desired row
                     if (posItem != null && posItem.viewType == item.viewType &&
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
new file mode 100644
index 0000000..0214c35
--- /dev/null
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -0,0 +1,168 @@
+/*
+ * 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.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.util.Themes;
+
+import java.util.List;
+
+/**
+ * ItemDecoration class that groups items in {@link AllAppsRecyclerView}
+ */
+public class AllAppsSectionDecorator extends RecyclerView.ItemDecoration {
+
+    private final AllAppsContainerView mAppsView;
+
+    AllAppsSectionDecorator(AllAppsContainerView appsContainerView) {
+        mAppsView = appsContainerView;
+    }
+
+    @Override
+    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
+        // Iterate through views in recylerview and draw bounds around views in the same section.
+        // Since views in the same section will follow each other, we can skip to a last view in
+        // a section to get the bounds of the section without having to iterate on every item.
+        int itemCount = parent.getChildCount();
+        List<AllAppsGridAdapter.AdapterItem> adapterItems = mAppsView.getApps().getAdapterItems();
+        SectionDecorationHandler lastDecorationHandler = null;
+        int i = 0;
+        while (i < itemCount) {
+            View view = parent.getChildAt(i);
+            int position = parent.getChildAdapterPosition(view);
+            AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
+            if (adapterItem.searchSectionInfo != null) {
+                SearchSectionInfo sectionInfo = adapterItem.searchSectionInfo;
+                int endIndex = Math.min(i + sectionInfo.getPosEnd() - position, itemCount - 1);
+                SectionDecorationHandler decorationHandler = sectionInfo.getDecorationHandler();
+                if (decorationHandler != lastDecorationHandler && lastDecorationHandler != null) {
+                    drawDecoration(c, lastDecorationHandler, parent);
+                }
+                lastDecorationHandler = decorationHandler;
+                if (decorationHandler != null) {
+                    decorationHandler.extendBounds(view);
+                }
+
+                if (endIndex > i) {
+                    i = endIndex;
+                    continue;
+                }
+            }
+            i++;
+        }
+        if (lastDecorationHandler != null) {
+            drawDecoration(c, lastDecorationHandler, parent);
+        }
+    }
+
+    private void drawDecoration(Canvas c, SectionDecorationHandler decorationHandler,
+            RecyclerView parent) {
+        if (decorationHandler == null) return;
+        if (decorationHandler.mIsFullWidth) {
+            decorationHandler.mBounds.left = parent.getPaddingLeft();
+            decorationHandler.mBounds.right = parent.getWidth() - parent.getPaddingRight();
+        }
+        decorationHandler.onDraw(c);
+        if (mAppsView.getFloatingHeaderView().getFocusedChild() == null
+                && mAppsView.getApps().getFocusedChild() != null) {
+            int index = mAppsView.getApps().getFocusedChildIndex();
+            if (index >= 0 && index < parent.getChildCount()) {
+                decorationHandler.onFocusDraw(c, parent.getChildAt(index));
+            }
+        }
+        decorationHandler.reset();
+    }
+
+    /**
+     * Handles grouping and drawing of items in the same all apps sections.
+     */
+    public static class SectionDecorationHandler {
+        private static final int FILL_ALPHA = (int) (.3f * 255);
+        private static final int FOCUS_ALPHA = (int) (.8f * 255);
+
+        protected RectF mBounds = new RectF();
+        private final boolean mIsFullWidth;
+        private final float mRadius;
+
+        protected int mFocusColor;
+        protected int mFillcolor;
+        private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+
+
+        public SectionDecorationHandler(Context context, boolean isFullWidth) {
+            mIsFullWidth = isFullWidth;
+            int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+            mFillcolor = ColorUtils.setAlphaComponent(endScrim, FILL_ALPHA);
+            mFocusColor = ColorUtils.setAlphaComponent(endScrim, FOCUS_ALPHA);
+            mRadius = Themes.getDialogCornerRadius(context);
+        }
+
+        /**
+         * Extends current bounds to include the view.
+         */
+        public void extendBounds(View view) {
+            if (mBounds.isEmpty()) {
+                mBounds.set(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+            } else {
+                mBounds.set(
+                        Math.min(mBounds.left, view.getLeft()),
+                        Math.min(mBounds.top, view.getTop()),
+                        Math.max(mBounds.right, view.getRight()),
+                        Math.max(mBounds.bottom, view.getBottom())
+                );
+            }
+        }
+
+        /**
+         * Draw bounds onto canvas.
+         */
+        public void onDraw(Canvas canvas) {
+            mPaint.setColor(mFillcolor);
+            canvas.drawRoundRect(mBounds, mRadius, mRadius, mPaint);
+        }
+
+        /**
+         * Draw the bound of the view to the canvas.
+         */
+        public void onFocusDraw(Canvas canvas, @Nullable View view) {
+            if (view == null) {
+                return;
+            }
+            mPaint.setColor(mFocusColor);
+            canvas.drawRoundRect(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
+        }
+
+        /**
+         * Reset view bounds to empty.
+         */
+        public void reset() {
+            mBounds.setEmpty();
+        }
+    }
+
+}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index a9b030e..4195a05 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -1,10 +1,24 @@
+/*
+ * 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.allapps;
 
 import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
@@ -14,31 +28,29 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.content.Context;
 import android.util.FloatProperty;
 import android.view.View;
-import android.view.ViewGroup;
 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.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.views.ScrimView;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
-import com.android.systemui.plugins.PluginListener;
 
 /**
  * Handles AllApps view transition.
@@ -51,7 +63,7 @@
  * closer to top or closer to the page indicator.
  */
 public class AllAppsTransitionController implements StateHandler<LauncherState>,
-        OnDeviceProfileChangeListener, PluginListener<AllAppsSearchPlugin> {
+        OnDeviceProfileChangeListener {
 
     public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
             new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -85,10 +97,7 @@
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
     private float mScrollRangeDelta = 0;
-
-    // plugin related variables
-    private AllAppsSearchPlugin mPlugin;
-    private View mPluginContent;
+    private AllAppsInsetTransitionController mInsetController;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
@@ -103,6 +112,10 @@
         return mShiftRange;
     }
 
+    public AllAppsInsetTransitionController getInsetController() {
+        return mInsetController;
+    }
+
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mIsVerticalLayout = dp.isVerticalBarLayout();
@@ -130,8 +143,8 @@
         float shiftCurrent = progress * mShiftRange;
 
         mAppsView.setTranslationY(shiftCurrent);
-        if (mPlugin != null) {
-            mPlugin.setProgress(progress);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mInsetController.setProgress(progress);
         }
     }
 
@@ -201,20 +214,13 @@
         Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
         Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
 
-        if (mPlugin == null) {
-            setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
-            mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
-                    hasAllAppsContent, setter, headerFade, allAppsFade);
-        } else {
-            setter.setViewAlpha(mPluginContent, hasAllAppsContent ? 1 : 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getContentView(), 0, allAppsFade);
-            setter.setViewAlpha(mAppsView.getScrollBar(), 0, allAppsFade);
-        }
-        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
-        setter.setInt(mScrimView, ScrimView.DRAG_HANDLE_ALPHA,
-                (visibleElements & VERTICAL_SWIPE_INDICATOR) != 0 ? 255 : 0, allAppsFade);
+        setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
+        setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
+        mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra,
+                hasAllAppsContent, setter, headerFade, allAppsFade);
+
+        mAppsView.getSearchUiManager().setContentVisibility(visibleElements, setter, allAppsFade);
 
         // Set visibility of the container at the very beginning or end of the transition.
         setter.setViewAlpha(mAppsView, hasAnyVisibleItem ? 1 : 0,
@@ -228,8 +234,12 @@
     public void setupViews(AllAppsContainerView appsView, ScrimView scrimView) {
         mAppsView = appsView;
         mScrimView = scrimView;
-        PluginManagerWrapper.INSTANCE.get(mLauncher)
-                .addPluginListener(this, AllAppsSearchPlugin.class, false);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+            mInsetController = new AllAppsInsetTransitionController(mShiftRange, mAppsView);
+            mLauncher.getSystemUiController().updateUiState(UI_STATE_ALLAPPS,
+                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+        }
     }
 
     /**
@@ -252,47 +262,15 @@
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
         }
-        updatePluginAnimationEnd();
-    }
-
-    @Override
-    public void onPluginConnected(AllAppsSearchPlugin plugin, Context context) {
-        mPlugin = plugin;
-        mPluginContent = mLauncher.getLayoutInflater().inflate(
-                R.layout.all_apps_content_layout, mAppsView, false);
-        mAppsView.addView(mPluginContent);
-        mPluginContent.setAlpha(0f);
-        mPlugin.setup((ViewGroup) mPluginContent, mLauncher, mShiftRange);
-    }
-
-    @Override
-    public void onPluginDisconnected(AllAppsSearchPlugin plugin) {
-        mPlugin = null;
-        mAppsView.removeView(mPluginContent);
-    }
-
-    public void onActivityDestroyed() {
-        PluginManagerWrapper.INSTANCE.get(mLauncher).removePluginListener(this);
-    }
-
-    /** Used for the plugin to signal when drag starts happens
-     * @param toAllApps*/
-    public void onDragStart(boolean toAllApps) {
-        if (mPlugin == null) return;
-
-        if (toAllApps) {
-            EditText editText = mAppsView.getSearchUiManager().setTextSearchEnabled(true);
-            mPlugin.setEditText(editText);
-        }
-        mPlugin.onDragStart(toAllApps ? 1f : 0f);
-    }
-
-    private void updatePluginAnimationEnd() {
-        if (mPlugin == null) return;
-        mPlugin.onAnimationEnd(mProgress);
-        if (Float.compare(mProgress, 1f) == 0) {
-            mAppsView.getSearchUiManager().setTextSearchEnabled(false);
-            mPlugin.setEditText(null);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
+            mInsetController.onAnimationEnd(mProgress);
+            if (Float.compare(mProgress, 0f) == 0) {
+                EditText editText = mAppsView.getSearchUiManager().getEditText();
+                if (editText != null) {
+                    editText.requestFocus();
+                }
+            }
+            // TODO: should make the controller hide synchronously
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 06209bb..8c059d5 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -19,8 +19,10 @@
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.search.SearchSectionInfo;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LabelComparator;
 
@@ -60,61 +62,6 @@
         }
     }
 
-    /**
-     * Info about a particular adapter item (can be either section or app)
-     */
-    public static class AdapterItem {
-        /** Common properties */
-        // The index of this adapter item in the list
-        public int position;
-        // The type of this item
-        public int viewType;
-
-        /** App-only properties */
-        // The section name of this app.  Note that there can be multiple items with different
-        // sectionNames in the same section
-        public String sectionName = null;
-        // The row that this item shows up on
-        public int rowIndex;
-        // The index of this app in the row
-        public int rowAppIndex;
-        // The associated AppInfo for the app
-        public AppInfo appInfo = null;
-        // The index of this app not including sections
-        public int appIndex = -1;
-
-        public static AdapterItem asApp(int pos, String sectionName, AppInfo appInfo,
-                int appIndex) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_ICON;
-            item.position = pos;
-            item.sectionName = sectionName;
-            item.appInfo = appInfo;
-            item.appIndex = appIndex;
-            return item;
-        }
-
-        public static AdapterItem asEmptySearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asAllAppsDivider(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_ALL_APPS_DIVIDER;
-            item.position = pos;
-            return item;
-        }
-
-        public static AdapterItem asMarketSearch(int pos) {
-            AdapterItem item = new AdapterItem();
-            item.viewType = AllAppsGridAdapter.VIEW_TYPE_SEARCH_MARKET;
-            item.position = pos;
-            return item;
-        }
-    }
 
     private final BaseDraggingActivity mLauncher;
 
@@ -122,8 +69,8 @@
     private final List<AppInfo> mApps = new ArrayList<>();
     private final AllAppsStore mAllAppsStore;
 
-    // The set of filtered apps with the current filter
-    private final List<AppInfo> mFilteredApps = new ArrayList<>();
+    // The number of results in current adapter
+    private int mAccessibilityResultsCount = 0;
     // The current set of adapter items
     private final ArrayList<AdapterItem> mAdapterItems = new ArrayList<>();
     // The set of sections that we allow fast-scrolling to (includes non-merged sections)
@@ -132,7 +79,7 @@
     private final boolean mIsWork;
 
     // The of ordered component names as a result of a search query
-    private ArrayList<ComponentKey> mSearchResults;
+    private ArrayList<AdapterItem> mSearchResults;
     private AllAppsGridAdapter mAdapter;
     private AppInfoComparator mAppNameComparator;
     private final int mNumAppsPerRow;
@@ -182,6 +129,25 @@
     }
 
     /**
+     * Returns the child adapter item with IME launch focus.
+     */
+    public AdapterItem getFocusedChild() {
+        return mAdapterItems.get(getFocusedChildIndex());
+    }
+
+    /**
+     * Returns the index of the child with IME launch focus.
+     */
+    public int getFocusedChildIndex() {
+        for (AdapterItem item : mAdapterItems) {
+            if (item.isCountedForAccessibility()) {
+                return mAdapterItems.indexOf(item);
+            }
+        }
+        return -1;
+    }
+
+    /**
      * Returns the number of rows of applications
      */
     public int getNumAppRows() {
@@ -192,7 +158,7 @@
      * Returns the number of applications in this list.
      */
     public int getNumFilteredApps() {
-        return mFilteredApps.size();
+        return mAccessibilityResultsCount;
     }
 
     /**
@@ -206,14 +172,14 @@
      * Returns whether there are no filtered results.
      */
     public boolean hasNoFilteredResults() {
-        return (mSearchResults != null) && mFilteredApps.isEmpty();
+        return (mSearchResults != null) && mAccessibilityResultsCount == 0;
     }
 
     /**
-     * Sets the sorted list of filtered components.
+     * Sets results list for search
      */
-    public boolean setOrderedFilter(ArrayList<ComponentKey> f) {
-        if (mSearchResults != f) {
+    public boolean setSearchResults(ArrayList<AdapterItem> f) {
+        if (f == null || mSearchResults != f) {
             boolean same = mSearchResults != null && mSearchResults.equals(f);
             mSearchResults = f;
             onAppsUpdated();
@@ -292,41 +258,75 @@
         int appIndex = 0;
 
         // Prepare to update the list of sections, filtered apps, etc.
-        mFilteredApps.clear();
+        mAccessibilityResultsCount = 0;
         mFastScrollerSections.clear();
         mAdapterItems.clear();
 
+        SearchSectionInfo appSection = new SearchSectionInfo();
+        appSection.setDecorationHandler(
+                new AllAppsSectionDecorator.SectionDecorationHandler(mLauncher, true));
+
         // Recreate the filtered and sectioned apps (for convenience for the grid layout) from the
         // ordered set of sections
-        for (AppInfo info : getFiltersAppInfos()) {
-            String sectionName = info.sectionName;
 
-            // Create a new section if the section names do not match
-            if (!sectionName.equals(lastSectionName)) {
-                lastSectionName = sectionName;
-                lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
-                mFastScrollerSections.add(lastFastScrollerSectionInfo);
-            }
+        if (!hasFilter()) {
+            mAccessibilityResultsCount = mApps.size();
+            appSection.setPosStart(position);
+            for (AppInfo info : mApps) {
+                String sectionName = info.sectionName;
 
-            // Create an app item
-            AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
-            if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
-                lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+                // Create a new section if the section names do not match
+                if (!sectionName.equals(lastSectionName)) {
+                    lastSectionName = sectionName;
+                    lastFastScrollerSectionInfo = new FastScrollSectionInfo(sectionName);
+                    mFastScrollerSections.add(lastFastScrollerSectionInfo);
+                }
+
+                // Create an app item
+                AdapterItem appItem = AdapterItem.asApp(position++, sectionName, info, appIndex++);
+                if (lastFastScrollerSectionInfo.fastScrollToItem == null) {
+                    lastFastScrollerSectionInfo.fastScrollToItem = appItem;
+                }
+                if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                    appItem.searchSectionInfo = appSection;
+                }
+                mAdapterItems.add(appItem);
             }
-            mAdapterItems.add(appItem);
-            mFilteredApps.add(info);
+            appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
+        } else {
+            List<AppInfo> appInfos = new ArrayList<>();
+            SearchSectionInfo lastSection = null;
+            for (int i = 0; i < mSearchResults.size(); i++) {
+                AdapterItem adapterItem = mSearchResults.get(i);
+                adapterItem.position = i;
+                mAdapterItems.add(adapterItem);
+                if (adapterItem.searchSectionInfo != lastSection) {
+                    if (adapterItem.searchSectionInfo != null) {
+                        adapterItem.searchSectionInfo.setPosStart(i);
+                    }
+                    if (lastSection != null) {
+                        lastSection.setPosEnd(i - 1);
+                    }
+                    lastSection = adapterItem.searchSectionInfo;
+                }
+                if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) {
+                    appInfos.add(adapterItem.appInfo);
+                }
+                if (adapterItem.isCountedForAccessibility()) {
+                    mAccessibilityResultsCount++;
+                }
+            }
+            if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+                // Append the search market item
+                if (hasNoFilteredResults()) {
+                    mAdapterItems.add(AdapterItem.asEmptySearch(position++));
+                } else {
+                    mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
+                }
+                mAdapterItems.add(AdapterItem.asMarketSearch(position++));
+
+            }
         }
-
-        if (hasFilter()) {
-            // Append the search market item
-            if (hasNoFilteredResults()) {
-                mAdapterItems.add(AdapterItem.asEmptySearch(position++));
-            } else {
-                mAdapterItems.add(AdapterItem.asAllAppsDivider(position++));
-            }
-            mAdapterItems.add(AdapterItem.asMarketSearch(position++));
-        }
-
         if (mNumAppsPerRow != 0) {
             // Update the number of rows in the adapter after we do all the merging (otherwise, we
             // would have to shift the values again)
@@ -381,18 +381,4 @@
             }
         }
     }
-
-    private List<AppInfo> getFiltersAppInfos() {
-        if (mSearchResults == null) {
-            return mApps;
-        }
-        ArrayList<AppInfo> result = new ArrayList<>();
-        for (ComponentKey key : mSearchResults) {
-            AppInfo match = mAllAppsStore.getApp(key);
-            if (match != null) {
-                result.add(match);
-            }
-        }
-        return result;
-    }
 }
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index b4ff5ea..14595ca 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -116,11 +116,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_DISCOVERY_BOUNCE) != 0;
     }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderRow.java b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
index f899587..e357f61 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderRow.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderRow.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.allapps;
 
 import android.graphics.Rect;
+import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.DeviceProfile;
@@ -55,4 +56,9 @@
     void setVerticalScroll(int scroll, boolean isScrolledOut);
 
     Class<? extends FloatingHeaderRow> getTypeClass();
+
+    /**
+     * Returns a child that has focus to be launched by the IME.
+     */
+    View getFocusedChild();
 }
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 81e1b94..11d3fb9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.systemui.plugins.AllAppsRow;
 import com.android.systemui.plugins.AllAppsRow.OnHeightUpdatedListener;
@@ -194,6 +195,19 @@
         onHeightUpdated();
     }
 
+    @Override
+    public View getFocusedChild() {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            for (FloatingHeaderRow row : mAllRows) {
+                if (row.hasVisibleContent() && row.shouldDraw()) {
+                    return row.getFocusedChild();
+                }
+            }
+            return null;
+        }
+        return super.getFocusedChild();
+    }
+
     public void setup(AllAppsContainerView.AdapterHolder[] mAH, boolean tabsHidden) {
         for (FloatingHeaderRow row : mAllRows) {
             row.setup(this, mAllRows, tabsHidden);
diff --git a/src/com/android/launcher3/allapps/PluginHeaderRow.java b/src/com/android/launcher3/allapps/PluginHeaderRow.java
index 3089b18..cf7142c 100644
--- a/src/com/android/launcher3/allapps/PluginHeaderRow.java
+++ b/src/com/android/launcher3/allapps/PluginHeaderRow.java
@@ -83,4 +83,9 @@
     public Class<PluginHeaderRow> getTypeClass() {
         return PluginHeaderRow.class;
     }
+
+    @Override
+    public View getFocusedChild() {
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/SearchUiManager.java b/src/com/android/launcher3/allapps/SearchUiManager.java
index 7d5363f..aa056a0 100644
--- a/src/com/android/launcher3/allapps/SearchUiManager.java
+++ b/src/com/android/launcher3/allapps/SearchUiManager.java
@@ -66,12 +66,8 @@
     }
 
     /**
-     * Called to control how the search UI result should be handled.
-     *
-     * @param isEnabled when {@code true}, the search is all handled inside AOSP
-     *                  and is not overlayable.
-     * @return the searchbox edit text object
+     * @return the edit text object
      */
     @Nullable
-    EditText setTextSearchEnabled(boolean isEnabled);
+    EditText getEditText();
 }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index df1cd26..3320189 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
@@ -27,11 +28,17 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ExtendedEditText;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * An interface to a search box that AllApps can command.
@@ -50,12 +57,13 @@
     public void setVisibility(int visibility) {
         mInput.setVisibility(visibility);
     }
+
     /**
      * Sets the references to the apps model and the search result callback.
      */
     public final void initialize(
             SearchAlgorithm searchAlgorithm, ExtendedEditText input,
-            BaseDraggingActivity launcher, Callbacks cb) {
+            BaseDraggingActivity launcher, Callbacks cb, Consumer<List<Bundle>> secondaryCb) {
         mCb = cb;
         mLauncher = launcher;
 
@@ -68,8 +76,11 @@
     }
 
     @Override
-    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-        // Do nothing
+    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+        if (mSearchAlgorithm instanceof PluginWrapper) {
+            ((PluginWrapper) mSearchAlgorithm).runOnPluginIfConnected(
+                    AllAppsSearchPlugin::startedSearchSession);
+        }
     }
 
     @Override
@@ -100,6 +111,15 @@
 
     @Override
     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            if (actionId == EditorInfo.IME_ACTION_SEARCH) {
+                // selectFocusedView should return SearchTargetEvent that is passed onto onClick
+                if (Launcher.getLauncher(mLauncher).getAppsView().selectFocusedView(v)) {
+                    return true;
+                }
+            }
+        }
+
         // Skip if it's not the right action
         if (actionId != EditorInfo.IME_ACTION_SEARCH) {
             return false;
@@ -157,16 +177,26 @@
     }
 
     /**
+     * A wrapper setup for running essential calls to plugin from search controller
+     */
+    public interface PluginWrapper {
+        /**
+         * executes call if plugin is connected
+         */
+        void runOnPluginIfConnected(Consumer<AllAppsSearchPlugin> plugin);
+    }
+
+    /**
      * Callback for getting search results.
      */
     public interface Callbacks {
 
         /**
-         * Called when the search is complete.
+         * Called when the search from primary source is complete.
          *
-         * @param apps sorted list of matching components or null if in case of failure.
+         * @param items sorted list of search result adapter items.
          */
-        void onSearchResult(String query, ArrayList<ComponentKey> apps);
+        void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
 
         /**
          * Called when the search results should be cleared.
@@ -174,4 +204,15 @@
         void clearSearchResult();
     }
 
+    /**
+     * An interface for supporting dynamic search results
+     *
+     * @param <T> Type of payload
+     */
+    public interface PayloadResultHandler<T> {
+        /**
+         * Updates View using Adapter's payload
+         */
+        void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 356c52c..7518521 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.method.TextKeyListener;
@@ -38,22 +39,25 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Insettable;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
 import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.util.ComponentKey;
 
 import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Layout to contain the All-apps search UI.
  */
 public class AppsSearchContainerLayout extends ExtendedEditText
         implements SearchUiManager, AllAppsSearchBarController.Callbacks,
-        AllAppsStore.OnUpdateListener, Insettable {
+        AllAppsStore.OnUpdateListener, Insettable, Consumer<List<Bundle>> {
 
     private final BaseDraggingActivity mLauncher;
     private final AllAppsSearchBarController mSearchBarController;
@@ -135,7 +139,8 @@
         mApps = appsView.getApps();
         mAppsView = appsView;
         mSearchBarController.initialize(
-                new DefaultAppSearchAlgorithm(mApps.getApps()), this, mLauncher, this);
+                new DefaultAppSearchAlgorithm(mLauncher, LauncherAppState.getInstance(mLauncher)),
+                this, mLauncher, this, this);
     }
 
     @Override
@@ -168,9 +173,9 @@
     }
 
     @Override
-    public void onSearchResult(String query, ArrayList<ComponentKey> apps) {
-        if (apps != null) {
-            mApps.setOrderedFilter(apps);
+    public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
+        if (items != null) {
+            mApps.setSearchResults(items);
             notifyResultChanged();
             mAppsView.setLastSearchQuery(query);
         }
@@ -178,7 +183,7 @@
 
     @Override
     public void clearSearchResult() {
-        if (mApps.setOrderedFilter(null)) {
+        if (mApps.setSearchResults(null)) {
             notifyResultChanged();
         }
 
@@ -216,7 +221,12 @@
     }
 
     @Override
-    public EditText setTextSearchEnabled(boolean isEnabled) {
+    public EditText getEditText() {
         return this;
     }
+
+    @Override
+    public void accept(List<Bundle> bundles) {
+        // TODO: Render the result on mAppsView object
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
new file mode 100644
index 0000000..dc9c155
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -0,0 +1,88 @@
+/*
+ * 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.search;
+
+import android.content.Context;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
+import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.data.AppInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A device search section for handling app searches
+ */
+public class AppsSearchPipeline implements SearchPipeline {
+
+    private static final int MAX_RESULTS_COUNT = 5;
+
+    private final SearchSectionInfo mSearchSectionInfo;
+    private final LauncherAppState mLauncherAppState;
+
+    public AppsSearchPipeline(Context context, LauncherAppState launcherAppState) {
+        mLauncherAppState = launcherAppState;
+        mSearchSectionInfo = new SearchSectionInfo();
+        mSearchSectionInfo.setDecorationHandler(
+                new SectionDecorationHandler(context, true));
+    }
+
+    @Override
+    public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
+        mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
+            @Override
+            public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+                List<AppInfo> matchingResults = getTitleMatchResult(apps.data, query);
+                callback.accept(getAdapterItems(matchingResults));
+            }
+        });
+    }
+
+    /**
+     * Filters {@link AppInfo}s matching specified query
+     */
+    public static ArrayList<AppInfo> getTitleMatchResult(List<AppInfo> apps, String query) {
+        // Do an intersection of the words in the query and each title, and filter out all the
+        // apps that don't match all of the words in the query.
+        final String queryTextLower = query.toLowerCase();
+        final ArrayList<AppInfo> result = new ArrayList<>();
+        DefaultAppSearchAlgorithm.StringMatcher matcher =
+                DefaultAppSearchAlgorithm.StringMatcher.getInstance();
+        for (AppInfo info : apps) {
+            if (DefaultAppSearchAlgorithm.matches(info, queryTextLower, matcher)) {
+                result.add(info);
+            }
+        }
+        return result;
+    }
+
+    private ArrayList<AdapterItem> getAdapterItems(List<AppInfo> matchingApps) {
+        ArrayList<AdapterItem> items = new ArrayList<>();
+        for (int i = 0; i < matchingApps.size() && i < MAX_RESULTS_COUNT; i++) {
+            AdapterItem appItem = AdapterItem.asApp(i, "", matchingApps.get(i), i);
+            appItem.searchSectionInfo = mSearchSectionInfo;
+            items.add(appItem);
+        }
+
+        return items;
+    }
+}
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index f72a988..5ed7de5 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -15,26 +15,25 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.content.Context;
 import android.os.Handler;
 
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.util.ComponentKey;
 
 import java.text.Collator;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The default search implementation.
  */
 public class DefaultAppSearchAlgorithm implements SearchAlgorithm {
 
-    private final List<AppInfo> mApps;
     protected final Handler mResultHandler;
+    private final AppsSearchPipeline mAppsSearchPipeline;
 
-    public DefaultAppSearchAlgorithm(List<AppInfo> apps) {
-        mApps = apps;
+    public DefaultAppSearchAlgorithm(Context context, LauncherAppState launcherAppState) {
         mResultHandler = new Handler();
+        mAppsSearchPipeline = new AppsSearchPipeline(context, launcherAppState);
     }
 
     @Override
@@ -47,28 +46,8 @@
     @Override
     public void doSearch(final String query,
             final AllAppsSearchBarController.Callbacks callback) {
-        final ArrayList<ComponentKey> result = getTitleMatchResult(query);
-        mResultHandler.post(new Runnable() {
-
-            @Override
-            public void run() {
-                callback.onSearchResult(query, result);
-            }
-        });
-    }
-
-    private ArrayList<ComponentKey> getTitleMatchResult(String query) {
-        // Do an intersection of the words in the query and each title, and filter out all the
-        // apps that don't match all of the words in the query.
-        final String queryTextLower = query.toLowerCase();
-        final ArrayList<ComponentKey> result = new ArrayList<>();
-        StringMatcher matcher = StringMatcher.getInstance();
-        for (AppInfo info : mApps) {
-            if (matches(info, queryTextLower, matcher)) {
-                result.add(info.toComponentKey());
-            }
-        }
-        return result;
+        mAppsSearchPipeline.performSearch(query,
+                results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
     }
 
     public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java
new file mode 100644
index 0000000..545f0e3
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java
@@ -0,0 +1,32 @@
+/*
+ * 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.search;
+
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+
+import java.util.ArrayList;
+import java.util.function.Consumer;
+
+/**
+ * An interface for handling search within pipeline
+ */
+public interface SearchPipeline {
+
+    /**
+     * Perform query
+     */
+    void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
+}
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
new file mode 100644
index 0000000..e026e84
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
@@ -0,0 +1,70 @@
+/*
+ * 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.search;
+
+import com.android.launcher3.allapps.AllAppsSectionDecorator.SectionDecorationHandler;
+
+/**
+ * Info class for a search section
+ */
+public class SearchSectionInfo {
+
+    private String mTitle;
+    private SectionDecorationHandler mDecorationHandler;
+
+    public int getPosStart() {
+        return mPosStart;
+    }
+
+    public void setPosStart(int posStart) {
+        mPosStart = posStart;
+    }
+
+    public int getPosEnd() {
+        return mPosEnd;
+    }
+
+    public void setPosEnd(int posEnd) {
+        mPosEnd = posEnd;
+    }
+
+    private int mPosStart;
+    private int mPosEnd;
+
+    public SearchSectionInfo() {
+        this(null);
+    }
+
+    public SearchSectionInfo(String title) {
+        mTitle = title;
+    }
+
+    public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
+        mDecorationHandler = sectionDecorationHandler;
+    }
+
+
+    public SectionDecorationHandler getDecorationHandler() {
+        return mDecorationHandler;
+    }
+
+    /**
+     * Returns the section's title
+     */
+    public String getTitle() {
+        return mTitle == null ? "" : mTitle;
+    }
+}
diff --git a/src/com/android/launcher3/anim/AlphaUpdateListener.java b/src/com/android/launcher3/anim/AlphaUpdateListener.java
index 69716ea..eabd283 100644
--- a/src/com/android/launcher3/anim/AlphaUpdateListener.java
+++ b/src/com/android/launcher3/anim/AlphaUpdateListener.java
@@ -46,8 +46,7 @@
     }
 
     @Override
-    public void onAnimationStart(Animator animation) {
-        super.onAnimationStart(animation);
+    public void onAnimationStart(Animator arg0) {
         // We want the views to be visible for animation, so fade-in/out is visible
         mView.setVisibility(View.VISIBLE);
     }
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index b83417c..9905e81 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -19,8 +19,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 
-import androidx.annotation.CallSuper;
-
 /**
  * Extension of {@link AnimatorListenerAdapter} for listening for non-cancelled animations
  */
@@ -29,12 +27,6 @@
     protected boolean mCancelled = false;
 
     @Override
-    @CallSuper
-    public void onAnimationStart(Animator animation) {
-        mCancelled = false;
-    }
-
-    @Override
     public void onAnimationCancel(Animator animation) {
         mCancelled = true;
     }
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 31e0418..dcdfb6e 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -335,7 +335,6 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            super.onAnimationStart(animation);
             mCancelled = false;
             mDispatched = false;
         }
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 860cceb..8016b2d 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.content.Context;
 import android.graphics.Path;
@@ -198,6 +198,7 @@
         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.
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 4195933..5362575 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -149,7 +149,7 @@
             mProgressAnimator = null;
         }
         if (mAnimHolders.isEmpty()) {
-            // Add a dummy animation to that the duration is respected
+            // Add a placeholder animation to that the duration is respected
             add(ValueAnimator.ofFloat(0, 1).setDuration(mDuration));
         }
         return mAnim;
diff --git a/src/com/android/launcher3/anim/SpringAnimationBuilder.java b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
index a9702b4..bd52158 100644
--- a/src/com/android/launcher3/anim/SpringAnimationBuilder.java
+++ b/src/com/android/launcher3/anim/SpringAnimationBuilder.java
@@ -25,7 +25,7 @@
 import androidx.annotation.FloatRange;
 import androidx.dynamicanimation.animation.SpringForce;
 
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to build an object animator which follows the same path as a spring animation for
@@ -134,7 +134,7 @@
     }
 
     public SpringAnimationBuilder computeParams() {
-        int singleFrameMs = DefaultDisplay.getSingleFrameMs(mContext);
+        int singleFrameMs = DisplayController.getSingleFrameMs(mContext);
         double naturalFreq = Math.sqrt(mStiffness);
         double dampedFreq = naturalFreq * Math.sqrt(1 - mDampingRatio * mDampingRatio);
 
diff --git a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
index 1d32d1d..30c3417 100644
--- a/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
+++ b/src/com/android/launcher3/compat/AccessibilityManagerCompat.java
@@ -70,7 +70,8 @@
         final Bundle parcel = new Bundle();
         parcel.putInt(TestProtocol.STATE_FIELD, stateOrdinal);
 
-        sendEventToTest(accessibilityManager, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
+        sendEventToTest(
+                accessibilityManager, context, TestProtocol.SWITCHED_TO_STATE_MESSAGE, parcel);
         Log.d(TestProtocol.PERMANENT_DIAG_TAG, "sendStateEventToTest: " + stateOrdinal);
     }
 
@@ -78,22 +79,24 @@
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
-        sendEventToTest(accessibilityManager, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
+        sendEventToTest(accessibilityManager, context, TestProtocol.SCROLL_FINISHED_MESSAGE, null);
     }
 
     public static void sendPauseDetectedEventToTest(Context context) {
         final AccessibilityManager accessibilityManager = getAccessibilityManagerForTest(context);
         if (accessibilityManager == null) return;
 
-        sendEventToTest(accessibilityManager, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
+        sendEventToTest(accessibilityManager, context, TestProtocol.PAUSE_DETECTED_MESSAGE, null);
     }
 
     private static void sendEventToTest(
-            AccessibilityManager accessibilityManager, String eventTag, Bundle data) {
+            AccessibilityManager accessibilityManager,
+            Context context, String eventTag, Bundle data) {
         final AccessibilityEvent e = AccessibilityEvent.obtain(
                 AccessibilityEvent.TYPE_ANNOUNCEMENT);
         e.setClassName(eventTag);
         e.setParcelableData(data);
+        e.setPackageName(context.getApplicationContext().getPackageName());
         accessibilityManager.sendAccessibilityEvent(e);
     }
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 78e8b82..2627149 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -92,7 +92,7 @@
 
     // Keep as DeviceFlag to allow remote disable in emergency.
     public static final BooleanFlag ENABLE_SUGGESTED_ACTIONS_OVERVIEW = new DeviceFlag(
-            "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", false, "Show chip hints on the overview screen");
+            "ENABLE_SUGGESTED_ACTIONS_OVERVIEW", true, "Show chip hints on the overview screen");
 
 
     public static final BooleanFlag ENABLE_DEVICE_SEARCH = getDebugFlag(
@@ -142,10 +142,6 @@
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
             "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
 
-    public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
-            "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
-            + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
-
     // Keep as DeviceFlag for remote disable in emergency.
     public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
             "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
@@ -153,6 +149,9 @@
     public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
             "ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
 
+    public static final BooleanFlag ENABLE_OVERVIEW_CONTENT_PUSH = getDebugFlag(
+            "ENABLE_OVERVIEW_CONTENT_PUSH", false, "Show Content Push button in Overview Actions");
+
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
             "ENABLE_DATABASE_RESTORE", true,
             "Enable database restore when new restore session is created");
@@ -161,9 +160,9 @@
             "ENABLE_UNIVERSAL_SMARTSPACE", false,
             "Replace Smartspace with a version rendered by System UI.");
 
-    public static final BooleanFlag ENABLE_LSQ_VELOCITY_PROVIDER = getDebugFlag(
-            "ENABLE_LSQ_VELOCITY_PROVIDER", true,
-            "Use Least Square algorithm for motion pause detection.");
+    public static final BooleanFlag ENABLE_SYSTEM_VELOCITY_PROVIDER = getDebugFlag(
+            "ENABLE_SYSTEM_VELOCITY_PROVIDER", true,
+            "Use system VelocityTracker's algorithm for motion pause detection.");
 
     public static final BooleanFlag ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS =
             getDebugFlag(
@@ -178,8 +177,9 @@
             "SEPARATE_RECENTS_ACTIVITY", false,
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
-    public static final BooleanFlag USER_EVENT_DISPATCHER = new DeviceFlag(
-            "USER_EVENT_DISPATCHER", true, "User event dispatcher collects logs.");
+    public static final BooleanFlag ENABLE_MINIMAL_DEVICE = new DeviceFlag(
+            "ENABLE_MINIMAL_DEVICE", false,
+            "Allow user to toggle minimal device mode in launcher.");
 
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 0df6713..2d625c5 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -43,13 +43,13 @@
 import android.view.View.OnTouchListener;
 
 import com.android.launcher3.BaseActivity;
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.PinRequestHelper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
@@ -249,7 +249,7 @@
      */
     public void onPlaceAutomaticallyClick(View v) {
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
-            InstallShortcutReceiver.queueShortcut(mRequest.getShortcutInfo(), this);
+            ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo());
             logCommand(Action.Command.CONFIRM);
             mRequest.accept();
             finish();
@@ -270,7 +270,8 @@
     }
 
     private void acceptWidget(int widgetId) {
-        InstallShortcutReceiver.queueWidget(mRequest.getAppWidgetProviderInfo(this), widgetId, this);
+        ItemInstallQueue.INSTANCE.get(this)
+                .queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId);
         mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
         mRequest.accept(mWidgetOptions);
         logCommand(Action.Command.CONFIRM);
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 03028d3..ef666f0 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -463,10 +463,10 @@
     }
 
     public void forceTouchMove() {
-        int[] dummyCoordinates = mCoordinatesTemp;
-        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates);
-        mDragObject.x = dummyCoordinates[0];
-        mDragObject.y = dummyCoordinates[1];
+        int[] placeholderCoordinates = mCoordinatesTemp;
+        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, placeholderCoordinates);
+        mDragObject.x = placeholderCoordinates[0];
+        mDragObject.y = placeholderCoordinates[1];
         checkTouchMove(dropTarget);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index de0fa1a..86b93d0 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -187,9 +187,6 @@
      */
     @TargetApi(Build.VERSION_CODES.O)
     public void setItemInfo(final ItemInfo info) {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION &&
                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
                 info.itemType != LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index bf3aa7f..6104d80 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.annotation.TargetApi;
 import android.appwidget.AppWidgetManager;
@@ -33,15 +32,11 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.PendingAddItemInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.PendingItemDragHelper;
 import com.android.launcher3.widget.WidgetAddFlowHandler;
 
-import java.util.ArrayList;
-
 /**
  * {@link DragSource} for handling drop from a different window. This object is initialized
  * in the source window and is passed on to the Launcher activity as an Intent extra.
@@ -107,12 +102,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> parents) {
-        parents.add(newContainerTarget(LauncherLogProto.ContainerType.PINITEM));
-    }
-
-    @Override
     protected void postCleanup() {
         super.postCleanup();
         mCancelSignal.cancel();
diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
index 9982b39..f543e47 100644
--- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
+++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java
@@ -49,14 +49,14 @@
 
     // Class name used in the target component, such that it will never represent an
     // actual existing class.
-    private static final String DUMMY_COMPONENT_CLASS = "pinned-shortcut";
+    private static final String STUB_COMPONENT_CLASS = "pinned-shortcut";
 
     private final PinItemRequest mRequest;
     private final ShortcutInfo mInfo;
     private final Context mContext;
 
     public PinShortcutRequestActivityInfo(PinItemRequest request, Context context) {
-        super(new ComponentName(request.getShortcutInfo().getPackage(), DUMMY_COMPONENT_CLASS),
+        super(new ComponentName(request.getShortcutInfo().getPackage(), STUB_COMPONENT_CLASS),
                 request.getShortcutInfo().getUserHandle());
         mRequest = request;
         mInfo = request.getShortcutInfo();
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index b91d1c3..281598a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -17,13 +17,14 @@
 package com.android.launcher3.folder;
 
 import static android.text.TextUtils.isEmpty;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
 
@@ -34,8 +35,10 @@
 import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.graphics.Canvas;
+import android.graphics.Insets;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.os.Build;
 import android.text.InputType;
 import android.text.Selection;
 import android.text.TextUtils;
@@ -47,11 +50,17 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.AnimationUtils;
 import android.view.inputmethod.EditorInfo;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Alarm;
 import com.android.launcher3.BubbleTextView;
@@ -208,6 +217,8 @@
 
     private StatsLogManager mStatsLogManager;
 
+    @Nullable private FolderWindowInsetsAnimationCallback mFolderWindowInsetsAnimationCallback;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -252,6 +263,13 @@
         int measureSpec = MeasureSpec.UNSPECIFIED;
         mFooter.measure(measureSpec, measureSpec);
         mFooterHeight = mFooter.getMeasuredHeight();
+
+        if (Utilities.ATLEAST_R) {
+            mFolderWindowInsetsAnimationCallback =
+                    new FolderWindowInsetsAnimationCallback(DISPATCH_MODE_STOP, this);
+
+            setWindowInsetsAnimationCallback(mFolderWindowInsetsAnimationCallback);
+        }
     }
 
     public boolean onLongClick(View v) {
@@ -373,6 +391,26 @@
         return false;
     }
 
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
+        if (Utilities.ATLEAST_R) {
+            this.setTranslationY(0);
+
+            if (windowInsets.isVisible(WindowInsets.Type.ime())) {
+                Insets keyboardInsets = windowInsets.getInsets(WindowInsets.Type.ime());
+                int folderHeightFromBottom = getHeightFromBottom();
+
+                if (keyboardInsets.bottom > folderHeightFromBottom) {
+                    // Translate this folder above the keyboard, then add the folder name's padding
+                    this.setTranslationY(folderHeightFromBottom - keyboardInsets.bottom
+                            - mFolderName.getPaddingBottom());
+                }
+            }
+        }
+
+        return windowInsets;
+    }
+
     public FolderIcon getFolderIcon() {
         return mFolderIcon;
     }
@@ -720,11 +758,17 @@
         a.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
+                if (Utilities.ATLEAST_R) {
+                    setWindowInsetsAnimationCallback(null);
+                }
                 mIsAnimatingClosed = true;
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (Utilities.ATLEAST_R && mFolderWindowInsetsAnimationCallback != null) {
+                    setWindowInsetsAnimationCallback(mFolderWindowInsetsAnimationCallback);
+                }
                 closeComplete(true);
                 announceAccessibilityChanges();
                 mIsAnimatingClosed = false;
@@ -1183,7 +1227,8 @@
                         newIcon.requestFocus();
                     }
                     if (finalItem != null) {
-                        mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
+                        mStatsLogManager.logger().withItemInfo(finalItem)
+                                .log(LAUNCHER_FOLDER_CONVERTED_TO_ICON);
                     }
                 }
             }
@@ -1481,27 +1526,6 @@
         outRect.right += mScrollAreaOffset;
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
-            ArrayList<LauncherLogProto.Target> targets) {
-        child.gridX = childInfo.cellX;
-        child.gridY = childInfo.cellY;
-        child.pageIndex = mContent.getCurrentPage();
-
-        LauncherLogProto.Target target = newContainerTarget(LauncherLogProto.ContainerType.FOLDER);
-        target.pageIndex = mInfo.screenId;
-        target.gridX = mInfo.cellX;
-        target.gridY = mInfo.cellY;
-        targets.add(target);
-
-        // continue to parent
-        if (mInfo.container == CONTAINER_HOTSEAT) {
-            mLauncher.getHotseat().fillInLogContainerData(mInfo, target, targets);
-        } else {
-            mLauncher.getWorkspace().fillInLogContainerData(mInfo, target, targets);
-        }
-    }
-
     private class OnScrollHintListener implements OnAlarmListener {
 
         private final DragObject mDragObject;
@@ -1589,17 +1613,6 @@
         return getOpenView(launcher, TYPE_FOLDER);
     }
 
-    @Override
-    public void logActionCommand(int command) {
-        mLauncher.getUserEventDispatcher().logActionCommand(
-                command, getFolderIcon(), getLogContainerType());
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.FOLDER;
-    }
-
     /**
      * Navigation bar back key or hardware input back key has been issued.
      */
@@ -1677,4 +1690,64 @@
         mLauncher.getUserEventDispatcher()
                 .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState));
     }
+
+    /** Returns the height of the current folder's bottom edge from the bottom of the screen. */
+    private int getHeightFromBottom() {
+        DragLayer.LayoutParams layoutParams = (DragLayer.LayoutParams) getLayoutParams();
+        int folderBottomPx = layoutParams.y + layoutParams.height;
+        int windowBottomPx = mLauncher.getDeviceProfile().heightPx;
+
+        return windowBottomPx - folderBottomPx;
+    }
+
+    /** Callback that animates a folder sliding up above the ime. */
+    @RequiresApi(api = Build.VERSION_CODES.R)
+    private static class FolderWindowInsetsAnimationCallback
+            extends WindowInsetsAnimation.Callback {
+
+        private final Folder mFolder;
+        float mFolderTranslationStart;
+        float mFolderTranslationEnd;
+
+        FolderWindowInsetsAnimationCallback(int dispatchMode, Folder folder) {
+            super(dispatchMode);
+
+            mFolder = folder;
+        }
+
+        @Override
+        public void onPrepare(@NonNull WindowInsetsAnimation animation) {
+            mFolderTranslationStart = mFolder.getTranslationY();
+        }
+
+        @NonNull
+        @Override
+        public WindowInsetsAnimation.Bounds onStart(
+                @NonNull WindowInsetsAnimation animation,
+                @NonNull WindowInsetsAnimation.Bounds bounds) {
+            mFolderTranslationEnd = mFolder.getTranslationY();
+
+            mFolder.setTranslationY(mFolderTranslationStart);
+
+            return super.onStart(animation, bounds);
+        }
+
+        @NonNull
+        @Override
+        public WindowInsets onProgress(@NonNull WindowInsets windowInsets,
+                @NonNull List<WindowInsetsAnimation> list) {
+            if (list.size() == 0) {
+                mFolder.setTranslationY(0);
+
+                return windowInsets;
+            }
+            float progress = list.get(0).getInterpolatedFraction();
+
+            mFolder.setTranslationY(
+                    Utilities.mapRange(progress, mFolderTranslationStart, mFolderTranslationEnd));
+
+            return windowInsets;
+        }
+
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 75275b2..32d061c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -129,6 +129,8 @@
     private float mDotScale;
     private Animator mDotScaleAnim;
 
+    private Rect mTouchArea = new Rect();
+
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
     private float mScaleForReorderBounce = 1f;
@@ -711,6 +713,11 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (event.getAction() == MotionEvent.ACTION_DOWN
+                && shouldIgnoreTouchDown(event.getX(), event.getY())) {
+            return false;
+        }
+
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
         super.onTouchEvent(event);
@@ -719,6 +726,15 @@
         return true;
     }
 
+    /**
+     * Returns true if the touch down at the provided position be ignored
+     */
+    protected boolean shouldIgnoreTouchDown(float x, float y) {
+        mTouchArea.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
+                getHeight() - getPaddingBottom());
+        return !mTouchArea.contains((int) x, (int) y);
+    }
+
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
index 4369385..b208a40 100644
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ b/src/com/android/launcher3/graphics/IconShape.java
@@ -43,8 +43,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
@@ -59,8 +60,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import androidx.annotation.Nullable;
-
 /**
  * Abstract representation of the shape of an icon shape
  */
@@ -381,9 +380,6 @@
      * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
      */
     public static void init(Context context) {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         pickBestShape(context);
     }
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index a424f84..cd84c96 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -19,6 +19,7 @@
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 import static android.view.View.VISIBLE;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
@@ -72,11 +73,12 @@
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.LoaderResults;
 import com.android.launcher3.model.LoaderTask;
+import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetsModel;
-import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -94,6 +96,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -116,7 +119,8 @@
  *   4) Measure and draw the view on a canvas
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class LauncherPreviewRenderer {
+public class LauncherPreviewRenderer extends ContextThemeWrapper
+        implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
     private static final String TAG = "LauncherPreviewRenderer";
 
@@ -210,10 +214,14 @@
     private final DeviceProfile mDp;
     private final boolean mMigrated;
     private final Rect mInsets;
-
     private final WorkspaceItemInfo mWorkspaceItemInfo;
+    private final LayoutInflater mHomeElementInflater;
+    private final InsettableFrameLayout mRootView;
+    private final Hotseat mHotseat;
+    private final CellLayout mWorkspace;
 
     public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
+        super(context, R.style.AppTheme);
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
@@ -238,291 +246,288 @@
         mWorkspaceItemInfo.intent = new Intent();
         mWorkspaceItemInfo.contentDescription = mWorkspaceItemInfo.title =
                 context.getString(R.string.label_application);
+
+        mHomeElementInflater = LayoutInflater.from(
+                new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
+        mHomeElementInflater.setFactory2(this);
+
+        mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
+                R.layout.launcher_preview_layout, null, false);
+        mRootView.setInsets(mInsets);
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+
+        mHotseat = mRootView.findViewById(R.id.hotseat);
+        mHotseat.resetLayout(false);
+
+        mWorkspace = mRootView.findViewById(R.id.workspace);
+        mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.top,
+                mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
+                mDp.workspacePadding.bottom);
     }
 
     /** Populate preview and render it. */
     public View getRenderedView() {
-        MainThreadRenderer renderer = new MainThreadRenderer(mContext);
-        renderer.populate();
-        return renderer.mRootView;
+        populate();
+        return mRootView;
     }
 
-    private class MainThreadRenderer extends ContextThemeWrapper
-            implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
+    public boolean shouldShowRealLauncherPreview() {
+        return ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get();
+    }
 
-        private final LayoutInflater mHomeElementInflater;
-        private final InsettableFrameLayout mRootView;
+    public boolean shouldShowQsb() {
+        return FeatureFlags.QSB_ON_FIRST_SCREEN;
+    }
 
-        private final Hotseat mHotseat;
-        private final CellLayout mWorkspace;
+    @Override
+    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
+        if ("TextClock".equals(name)) {
+            // Workaround for TextClock accessing handler for unregistering ticker.
+            return new TextClock(context, attrs) {
 
-        MainThreadRenderer(Context context) {
-            super(context, R.style.AppTheme);
-
-            mHomeElementInflater = LayoutInflater.from(
-                    new ContextThemeWrapper(this, R.style.HomeScreenElementTheme));
-            mHomeElementInflater.setFactory2(this);
-
-            mRootView = (InsettableFrameLayout) mHomeElementInflater.inflate(
-                    R.layout.launcher_preview_layout, null, false);
-            mRootView.setInsets(mInsets);
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-
-            mHotseat = mRootView.findViewById(R.id.hotseat);
-            mHotseat.resetLayout(false);
-
-            mWorkspace = mRootView.findViewById(R.id.workspace);
-            mWorkspace.setPadding(mDp.workspacePadding.left + mDp.cellLayoutPaddingLeftRightPx,
-                    mDp.workspacePadding.top,
-                    mDp.workspacePadding.right + mDp.cellLayoutPaddingLeftRightPx,
-                    mDp.workspacePadding.bottom);
+                @Override
+                public Handler getHandler() {
+                    return mUiHandler;
+                }
+            };
+        } else if (!"fragment".equals(name)) {
+            return null;
         }
 
-        @Override
-        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-            if ("TextClock".equals(name)) {
-                // Workaround for TextClock accessing handler for unregistering ticker.
-                return new TextClock(context, attrs) {
+        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
+        FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
+                context, ta.getString(R.styleable.PreviewFragment_android_name));
+        f.enterPreviewMode(context);
+        f.onInit(null);
 
-                    @Override
-                    public Handler getHandler() {
-                        return mUiHandler;
-                    }
-                };
-            } else if (!"fragment".equals(name)) {
-                return null;
-            }
+        View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
+        view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
+        return view;
+    }
 
-            TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PreviewFragment);
-            FragmentWithPreview f = (FragmentWithPreview) Fragment.instantiate(
-                    context, ta.getString(R.styleable.PreviewFragment_android_name));
-            f.enterPreviewMode(context);
-            f.onInit(null);
+    @Override
+    public View onCreateView(String name, Context context, AttributeSet attrs) {
+        return onCreateView(null, name, context, attrs);
+    }
 
-            View view = f.onCreateView(LayoutInflater.from(context), (ViewGroup) parent, null);
-            view.setId(ta.getInt(R.styleable.PreviewFragment_android_id, View.NO_ID));
-            return view;
+    @Override
+    public BaseDragLayer getDragLayer() {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public DeviceProfile getDeviceProfile() {
+        return mDp;
+    }
+
+    @Override
+    public Hotseat getHotseat() {
+        return mHotseat;
+    }
+
+    @Override
+    public CellLayout getScreenWithId(int screenId) {
+        return mWorkspace;
+    }
+
+    private void inflateAndAddIcon(WorkspaceItemInfo info) {
+        BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
+                R.layout.app_icon, mWorkspace, false);
+        icon.applyFromWorkspaceItem(info);
+        addInScreenFromBind(icon, info);
+    }
+
+    private void inflateAndAddFolder(FolderInfo info) {
+        FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
+                info);
+        addInScreenFromBind(folderIcon, info);
+    }
+
+    private void inflateAndAddWidgets(
+            LauncherAppWidgetInfo info,
+            Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
+        if (widgetProviderInfoMap == null) {
+            return;
         }
-
-        @Override
-        public View onCreateView(String name, Context context, AttributeSet attrs) {
-            return onCreateView(null, name, context, attrs);
+        AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get(
+                new ComponentKey(info.providerName, info.user));
+        if (providerInfo == null) {
+            return;
         }
+        inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo(
+                getApplicationContext(), providerInfo));
+    }
 
-        @Override
-        public BaseDragLayer getDragLayer() {
-            throw new UnsupportedOperationException();
+    private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
+        WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
+                info.providerName);
+        if (widgetItem == null) {
+            return;
         }
+        inflateAndAddWidgets(info, widgetItem.widgetInfo);
+    }
 
-        @Override
-        public DeviceProfile getDeviceProfile() {
-            return mDp;
-        }
+    private void inflateAndAddWidgets(
+            LauncherAppWidgetInfo info, LauncherAppWidgetProviderInfo providerInfo) {
+        AppWidgetHostView view = new AppWidgetHostView(mContext);
+        view.setAppWidget(-1, providerInfo);
+        view.updateAppWidget(null);
+        view.setTag(info);
+        addInScreenFromBind(view, info);
+    }
 
-        @Override
-        public Hotseat getHotseat() {
-            return mHotseat;
-        }
-
-        @Override
-        public CellLayout getScreenWithId(int screenId) {
-            return mWorkspace;
-        }
-
-        private void inflateAndAddIcon(WorkspaceItemInfo info) {
-            BubbleTextView icon = (BubbleTextView) mHomeElementInflater.inflate(
-                    R.layout.app_icon, mWorkspace, false);
-            icon.applyFromWorkspaceItem(info);
-            addInScreenFromBind(icon, info);
-        }
-
-        private void inflateAndAddFolder(FolderInfo info) {
-            FolderIcon folderIcon = FolderIcon.inflateIcon(R.layout.folder_icon, this, mWorkspace,
-                    info);
-            addInScreenFromBind(folderIcon, info);
-        }
-
-        private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
-                Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-            if (widgetProviderInfoMap == null) {
-                return;
-            }
-            AppWidgetProviderInfo providerInfo = widgetProviderInfoMap.get(
-                    new ComponentKey(info.providerName, info.user));
-            if (providerInfo == null) {
-                return;
-            }
-            inflateAndAddWidgets(info, LauncherAppWidgetProviderInfo.fromProviderInfo(
-                    getApplicationContext(), providerInfo));
-        }
-
-        private void inflateAndAddWidgets(LauncherAppWidgetInfo info, WidgetsModel widgetsModel) {
-            WidgetItem widgetItem = widgetsModel.getWidgetProviderInfoByProviderName(
-                    info.providerName);
-            if (widgetItem == null) {
-                return;
-            }
-            inflateAndAddWidgets(info, widgetItem.widgetInfo);
-        }
-
-        private void inflateAndAddWidgets(LauncherAppWidgetInfo info,
-                LauncherAppWidgetProviderInfo providerInfo) {
-            AppWidgetHostView view = new AppWidgetHostView(mContext);
-            view.setAppWidget(-1, providerInfo);
-            view.updateAppWidget(null);
-            view.setTag(info);
+    private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
+        View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
+        if (view != null) {
             addInScreenFromBind(view, info);
         }
+    }
 
-        private void inflateAndAddPredictedIcon(WorkspaceItemInfo info) {
-            View view = PredictedAppIconInflater.inflate(mHomeElementInflater, mWorkspace, info);
-            if (view != null) {
-                addInScreenFromBind(view, info);
-            }
+    private void dispatchVisibilityAggregated(View view, boolean isVisible) {
+        // Similar to View.dispatchVisibilityAggregated implementation.
+        final boolean thisVisible = view.getVisibility() == VISIBLE;
+        if (thisVisible || !isVisible) {
+            view.onVisibilityAggregated(isVisible);
         }
 
-        private void dispatchVisibilityAggregated(View view, boolean isVisible) {
-            // Similar to View.dispatchVisibilityAggregated implementation.
-            final boolean thisVisible = view.getVisibility() == VISIBLE;
-            if (thisVisible || !isVisible) {
-                view.onVisibilityAggregated(isVisible);
-            }
+        if (view instanceof ViewGroup) {
+            isVisible = thisVisible && isVisible;
+            ViewGroup vg = (ViewGroup) view;
+            int count = vg.getChildCount();
 
-            if (view instanceof ViewGroup) {
-                isVisible = thisVisible && isVisible;
-                ViewGroup vg = (ViewGroup) view;
-                int count = vg.getChildCount();
-
-                for (int i = 0; i < count; i++) {
-                    dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
-                }
+            for (int i = 0; i < count; i++) {
+                dispatchVisibilityAggregated(vg.getChildAt(i), isVisible);
             }
         }
+    }
 
-        private void populate() {
-            if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
-                WorkspaceFetcher fetcher;
-                PreviewContext previewContext = null;
-                if (mMigrated) {
-                    previewContext = new PreviewContext(mContext, mIdp);
-                    LauncherAppState appForPreview = new LauncherAppState(
-                            previewContext, null /* iconCacheFileName */);
-                    fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
-                    MODEL_EXECUTOR.execute(fetcher);
-                } else {
-                    fetcher = new WorkspaceItemsInfoFetcher();
-                    LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                            (LauncherModel.ModelUpdateTask) fetcher);
-                }
-                WorkspaceResult workspaceResult = fetcher.get();
-                if (previewContext != null) {
-                    previewContext.onDestroy();
-                }
-
-                if (workspaceResult == null) {
-                    return;
-                }
-
-                // Separate the items that are on the current screen, and all the other remaining
-                // items
-                ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
-                ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
-                ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
-                ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
-
-                filterCurrentWorkspaceItems(0 /* currentScreenId */,
-                        workspaceResult.mWorkspaceItems, currentWorkspaceItems,
-                        otherWorkspaceItems);
-                filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
-                        currentAppWidgets, otherAppWidgets);
-                sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
-
-                for (ItemInfo itemInfo : currentWorkspaceItems) {
-                    switch (itemInfo.itemType) {
-                        case Favorites.ITEM_TYPE_APPLICATION:
-                        case Favorites.ITEM_TYPE_SHORTCUT:
-                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
-                            break;
-                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                            inflateAndAddFolder((FolderInfo) itemInfo);
-                            break;
-                        default:
-                            break;
-                    }
-                }
-                for (ItemInfo itemInfo : currentAppWidgets) {
-                    switch (itemInfo.itemType) {
-                        case Favorites.ITEM_TYPE_APPWIDGET:
-                        case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                            if (mMigrated) {
-                                inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
-                                        workspaceResult.mWidgetProvidersMap);
-                            } else {
-                                inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
-                                        workspaceResult.mWidgetsModel);
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                }
-
-                IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
-                        mIdp.numHotseatIcons);
-                int count = Math.min(ranks.size(), workspaceResult.mCachedPredictedItems.size());
-                for (int i = 0; i < count; i++) {
-                    AppInfo appInfo = workspaceResult.mCachedPredictedItems.get(i);
-                    int rank = ranks.get(i);
-                    WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(appInfo);
-                    itemInfo.container = LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
-                    itemInfo.rank = rank;
-                    itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
-                    itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
-                    itemInfo.screenId = rank;
-                    inflateAndAddPredictedIcon(itemInfo);
-                }
+    private void populate() {
+        if (shouldShowRealLauncherPreview()) {
+            WorkspaceFetcher fetcher;
+            PreviewContext previewContext = null;
+            if (mMigrated) {
+                previewContext = new PreviewContext(mContext, mIdp);
+                LauncherAppState appForPreview = new LauncherAppState(
+                        previewContext, null /* iconCacheFileName */);
+                fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
+                MODEL_EXECUTOR.execute(fetcher);
             } else {
-                // Add hotseat icons
-                for (int i = 0; i < mIdp.numHotseatIcons; i++) {
-                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                    info.container = Favorites.CONTAINER_HOTSEAT;
-                    info.screenId = i;
-                    inflateAndAddIcon(info);
-                }
-                // Add workspace icons
-                for (int i = 0; i < mIdp.numColumns; i++) {
-                    WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
-                    info.container = Favorites.CONTAINER_DESKTOP;
-                    info.screenId = 0;
-                    info.cellX = i;
-                    info.cellY = mIdp.numRows - 1;
-                    inflateAndAddIcon(info);
-                }
+                fetcher = new WorkspaceItemsInfoFetcher();
+                LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
+                        (LauncherModel.ModelUpdateTask) fetcher);
+            }
+            WorkspaceResult workspaceResult = fetcher.get();
+            if (previewContext != null) {
+                previewContext.onDestroy();
             }
 
-            // Add first page QSB
-            if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
-                View qsb = mHomeElementInflater.inflate(
-                        R.layout.search_container_workspace, mWorkspace, false);
-                CellLayout.LayoutParams lp =
-                        new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
-                lp.canReorder = false;
-                mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+            if (workspaceResult == null) {
+                return;
             }
 
-            // Setup search view
-            SearchUiManager searchUiManager =
-                    mRootView.findViewById(R.id.search_container_all_apps);
-            mRootView.findViewById(R.id.apps_view).setTranslationY(
-                    mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
-
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-            dispatchVisibilityAggregated(mRootView, true);
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
-            // Additional measure for views which use auto text size API
-            measureView(mRootView, mDp.widthPx, mDp.heightPx);
+            // Separate the items that are on the current screen, and the other remaining items.
+            ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
+            ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
+            ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+            filterCurrentWorkspaceItems(0 /* currentScreenId */,
+                    workspaceResult.mWorkspaceItems, currentWorkspaceItems,
+                    otherWorkspaceItems);
+            filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets,
+                    currentAppWidgets, otherAppWidgets);
+            sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems);
+            for (ItemInfo itemInfo : currentWorkspaceItems) {
+                switch (itemInfo.itemType) {
+                    case Favorites.ITEM_TYPE_APPLICATION:
+                    case Favorites.ITEM_TYPE_SHORTCUT:
+                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                        break;
+                    case Favorites.ITEM_TYPE_FOLDER:
+                        inflateAndAddFolder((FolderInfo) itemInfo);
+                        break;
+                    default:
+                        break;
+                }
+            }
+            for (ItemInfo itemInfo : currentAppWidgets) {
+                switch (itemInfo.itemType) {
+                    case Favorites.ITEM_TYPE_APPWIDGET:
+                    case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                        if (mMigrated) {
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                    workspaceResult.mWidgetProvidersMap);
+                        } else {
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo,
+                                    workspaceResult.mWidgetsModel);
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+            IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
+                    mIdp.numHotseatIcons);
+            List<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
+                    ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
+            int count = Math.min(ranks.size(), predictions.size());
+            for (int i = 0; i < count; i++) {
+                int rank = ranks.get(i);
+                WorkspaceItemInfo itemInfo =
+                        new WorkspaceItemInfo((WorkspaceItemInfo) predictions.get(i));
+                itemInfo.container = CONTAINER_HOTSEAT_PREDICTION;
+                itemInfo.rank = rank;
+                itemInfo.cellX = mHotseat.getCellXFromOrder(rank);
+                itemInfo.cellY = mHotseat.getCellYFromOrder(rank);
+                itemInfo.screenId = rank;
+                inflateAndAddPredictedIcon(itemInfo);
+            }
+        } else {
+            // Add hotseat icons
+            for (int i = 0; i < mIdp.numHotseatIcons; i++) {
+                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                info.container = Favorites.CONTAINER_HOTSEAT;
+                info.screenId = i;
+                inflateAndAddIcon(info);
+            }
+            // Add workspace icons
+            for (int i = 0; i < mIdp.numColumns; i++) {
+                WorkspaceItemInfo info = new WorkspaceItemInfo(mWorkspaceItemInfo);
+                info.container = Favorites.CONTAINER_DESKTOP;
+                info.screenId = 0;
+                info.cellX = i;
+                info.cellY = mIdp.numRows - 1;
+                inflateAndAddIcon(info);
+            }
         }
+
+        // Add first page QSB
+        if (shouldShowQsb()) {
+            View qsb = mHomeElementInflater.inflate(
+                    R.layout.search_container_workspace, mWorkspace, false);
+            CellLayout.LayoutParams lp =
+                    new CellLayout.LayoutParams(0, 0, mWorkspace.getCountX(), 1);
+            lp.canReorder = false;
+            mWorkspace.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true);
+        }
+
+        // Setup search view
+        SearchUiManager searchUiManager =
+                mRootView.findViewById(R.id.search_container_all_apps);
+        mRootView.findViewById(R.id.apps_view).setTranslationY(
+                mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
+        ViewGroup searchView = (ViewGroup) searchUiManager;
+        searchView.setEnabled(false);
+        for (int i = 0; i < searchView.getChildCount(); i++) {
+            searchView.getChildAt(i).setEnabled(false);
+        }
+
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        dispatchVisibilityAggregated(mRootView, true);
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        // Additional measure for views which use auto text size API
+        measureView(mRootView, mDp.widthPx, mDp.heightPx);
     }
 
     private static void measureView(View view, int width, int height) {
@@ -568,8 +573,7 @@
                 return null;
             }
 
-            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, mBgDataModel.widgetsModel, null);
+            return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null);
         }
     }
 
@@ -579,7 +583,7 @@
         private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
 
         WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
-            super(app, null, new BgDataModel(), null);
+            super(app, null, new BgDataModel(), new ModelDelegate(), null);
         }
 
         @Override
@@ -593,11 +597,13 @@
         }
 
         @Override
-        public WorkspaceResult call() throws Exception {
+        public WorkspaceResult call() {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
-            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
-            return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
-                    mBgDataModel.cachedPredictedItems, null, mWidgetProvidersMap);
+            loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
+                    LauncherSettings.Favorites.SCREEN + " = 0 or "
+                            + LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+            return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap);
         }
     }
 
@@ -617,17 +623,16 @@
     private static class WorkspaceResult {
         private final ArrayList<ItemInfo> mWorkspaceItems;
         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
-        private final ArrayList<AppInfo> mCachedPredictedItems;
+        private final FixedContainerItems mHotseatPredictions;
         private final WidgetsModel mWidgetsModel;
         private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
 
-        private WorkspaceResult(ArrayList<ItemInfo> workspaceItems,
-                ArrayList<LauncherAppWidgetInfo> appWidgets,
-                ArrayList<AppInfo> cachedPredictedItems, WidgetsModel widgetsModel,
+        private WorkspaceResult(BgDataModel dataModel,
+                WidgetsModel widgetsModel,
                 Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-            mWorkspaceItems = workspaceItems;
-            mAppWidgets = appWidgets;
-            mCachedPredictedItems = cachedPredictedItems;
+            mWorkspaceItems = dataModel.workspaceItems;
+            mAppWidgets = dataModel.appWidgets;
+            mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
             mWidgetsModel = widgetsModel;
             mWidgetProvidersMap = widgetProviderInfoMap;
         }
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index 94acbfd..c0c3e5e 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -33,10 +34,25 @@
  */
 public class OverviewScrim extends Scrim {
 
+    public static final FloatProperty<OverviewScrim> SCRIM_MULTIPLIER =
+            new FloatProperty<OverviewScrim>("scrimMultiplier") {
+                @Override
+                public Float get(OverviewScrim scrim) {
+                    return scrim.mScrimMultiplier;
+                }
+
+                @Override
+                public void setValue(OverviewScrim scrim, float v) {
+                    scrim.setScrimMultiplier(v);
+                }
+            };
+
     private @NonNull View mStableScrimmedView;
     // Might be higher up if mStableScrimmedView is invisible.
     private @Nullable View mCurrentScrimmedView;
 
+    private float mScrimMultiplier = 1f;
+
     public OverviewScrim(View view) {
         super(view);
         mStableScrimmedView = mCurrentScrimmedView = mLauncher.getOverviewPanel();
@@ -68,4 +84,16 @@
     public @Nullable View getScrimmedView() {
         return mCurrentScrimmedView;
     }
+
+    private void setScrimMultiplier(float scrimMultiplier) {
+        if (Float.compare(mScrimMultiplier, scrimMultiplier) != 0) {
+            mScrimMultiplier = scrimMultiplier;
+            invalidate();
+        }
+    }
+
+    @Override
+    protected int getScrimAlpha() {
+        return Math.round(super.getScrimAlpha() * mScrimMultiplier);
+    }
 }
diff --git a/src/com/android/launcher3/graphics/Scrim.java b/src/com/android/launcher3/graphics/Scrim.java
index f90962d..a151cba 100644
--- a/src/com/android/launcher3/graphics/Scrim.java
+++ b/src/com/android/launcher3/graphics/Scrim.java
@@ -61,7 +61,11 @@
     }
 
     public void draw(Canvas canvas) {
-        canvas.drawColor(setColorAlphaBound(mScrimColor, mScrimAlpha));
+        canvas.drawColor(setColorAlphaBound(mScrimColor, getScrimAlpha()));
+    }
+
+    protected int getScrimAlpha() {
+        return mScrimAlpha;
     }
 
     private void setScrimProgress(float progress) {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index ff0f773..8013557 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -67,8 +67,8 @@
 
     private static final String TAG = "Launcher.IconCache";
 
-    private final Predicate<ItemInfoWithIcon> mIsUsingFallbackIconCheck = w -> w.bitmap != null
-            && w.bitmap.isNullOrLowRes() && !isDefaultIcon(w.bitmap, w.user);
+    private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w ->
+            w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user));
 
     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
@@ -192,14 +192,14 @@
      * Fill in {@param info} with the icon for {@param si}
      */
     public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
-        getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck);
+        getShortcutIcon(info, si, true, mIsUsingFallbackOrNonDefaultIconCheck);
     }
 
     /**
      * Fill in {@param info} with an unbadged icon for {@param si}
      */
     public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
-        getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck);
+        getShortcutIcon(info, si, false, mIsUsingFallbackOrNonDefaultIconCheck);
     }
 
     /**
@@ -260,14 +260,6 @@
     }
 
     /**
-     * Fill in info with the icon and label for deep shortcut.
-     */
-    public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
-        return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
-                () -> info, mShortcutCachingLogic, false, false);
-    }
-
-    /**
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
@@ -295,7 +287,7 @@
     /**
      * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
      */
-    private synchronized void getTitleAndIcon(
+    public synchronized void getTitleAndIcon(
             @NonNull ItemInfoWithIcon infoInOut,
             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
             boolean usePkgIcon, boolean useLowResIcon) {
diff --git a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
index c50189c..ae7ad10 100644
--- a/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
+++ b/src/com/android/launcher3/keyboard/FocusIndicatorHelper.java
@@ -32,6 +32,8 @@
 import android.view.View.OnFocusChangeListener;
 
 import com.android.launcher3.R;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.util.Themes;
 
 /**
  * A helper class to draw background of a focused view.
@@ -93,6 +95,7 @@
 
     private ObjectAnimator mCurrentAnimation;
     private float mAlpha;
+    private float mRadius;
 
     public FocusIndicatorHelper(View container) {
         mContainer = container;
@@ -104,6 +107,9 @@
 
         setAlpha(0);
         mShift = 0;
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            mRadius = Themes.getDialogCornerRadius(container.getContext());
+        }
     }
 
     protected void setAlpha(float alpha) {
@@ -129,13 +135,15 @@
     }
 
     public void draw(Canvas c) {
-        if (mAlpha > 0) {
-            Rect newRect = getDrawRect();
-            if (newRect != null) {
-                mDirtyRect.set(newRect);
-                c.drawRect(mDirtyRect, mPaint);
-                mIsDirty = true;
-            }
+        if (mAlpha <= 0) return;
+
+        Rect newRect = getDrawRect();
+        if (newRect != null) {
+            mDirtyRect.set(newRect);
+            c.drawRoundRect((float) mDirtyRect.left, (float) mDirtyRect.top,
+                    (float) mDirtyRect.right, (float) mDirtyRect.bottom,
+                    mRadius, mRadius, mPaint);
+            mIsDirty = true;
         }
     }
 
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index acf4482..ec1c3ef 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -30,11 +30,16 @@
 import com.android.launcher3.userevent.LauncherLogProto;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import java.util.List;
+
 /**
  * Handles the user event logging in R+.
+ *
+ * <pre>
  * All of the event ids are defined here.
- * Most of the methods are dummy methods for Launcher3
+ * Most of the methods are placeholder methods for Launcher3
  * Actual call happens only for Launcher variant that implements QuickStep.
+ * </pre>
  */
 public class StatsLogManager implements ResourceBasedOverride {
 
@@ -46,8 +51,8 @@
     public static final int LAUNCHER_STATE_UNCHANGED = 5;
 
     /**
-     * Returns proper launcher state enum for {@link StatsLogManager}
-     * (to be removed during UserEventDispatcher cleanup)
+     * Returns proper launcher state enum for {@link StatsLogManager}(to be removed during
+     * UserEventDispatcher cleanup)
      */
     public static int containerTypeToAtomState(int containerType) {
         switch (containerType) {
@@ -64,9 +69,8 @@
     }
 
     /**
-     * Returns event enum based on the two {@link ContainerType} transition information when
-     * swipe gesture happens.
-     * (to be removed during UserEventDispatcher cleanup)
+     * Returns event enum based on the two {@link ContainerType} transition information when swipe
+     * gesture happens(to be removed during UserEventDispatcher cleanup).
      */
     public static EventEnum getLauncherAtomEvent(int startContainerType,
             int targetContainerType, EventEnum fallbackEvent) {
@@ -270,7 +274,55 @@
         LAUNCHER_SELECT_MODE_CLOSE(583),
 
         @UiEvent(doc = "User tapped on the highlight items in select mode")
-        LAUNCHER_SELECT_MODE_ITEM(584);
+        LAUNCHER_SELECT_MODE_ITEM(584),
+
+        @UiEvent(doc = "Notification dot on app icon enabled.")
+        LAUNCHER_NOTIFICATION_DOT_ENABLED(611),
+
+        @UiEvent(doc = "Notification dot on app icon disabled.")
+        LAUNCHER_NOTIFICATION_DOT_DISABLED(612),
+
+        @UiEvent(doc = "For new apps, add app icons to home screen enabled.")
+        LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613),
+
+        @UiEvent(doc = "For new apps, add app icons to home screen disabled.")
+        LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614),
+
+        @UiEvent(doc = "Home screen rotation is enabled when phone is rotated.")
+        LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615),
+
+        @UiEvent(doc = "Home screen rotation is disabled when phone is rotated.")
+        LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616),
+
+        @UiEvent(doc = "Suggestions in all apps list enabled.")
+        LAUNCHER_ALL_APPS_SUGGESTIONS_ENABLED(619),
+
+        @UiEvent(doc = "Suggestions in all apps list disabled.")
+        LAUNCHER_ALL_APPS_SUGGESTIONS_DISABLED(620),
+
+        @UiEvent(doc = "Suggestions on home screen is enabled.")
+        LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED(621),
+
+        @UiEvent(doc = "Suggestions on home screen is disabled.")
+        LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED(622),
+
+        @UiEvent(doc = "System navigation is 3 button mode.")
+        LAUNCHER_NAVIGATION_MODE_3_BUTTON(623),
+
+        @UiEvent(doc = "System navigation mode is 2 button mode.")
+        LAUNCHER_NAVIGATION_MODE_2_BUTTON(624),
+
+        @UiEvent(doc = "System navigation mode is 0 button mode/gesture navigation mode .")
+        LAUNCHER_NAVIGATION_MODE_GESTURE_BUTTON(625),
+
+        @UiEvent(doc = "User tapped on image content in Overview Select mode.")
+        LAUNCHER_SELECT_MODE_IMAGE(627),
+
+        @UiEvent(doc = "A folder was replaced by a single item")
+        LAUNCHER_FOLDER_CONVERTED_TO_ICON(628),
+
+        @UiEvent(doc = "A hotseat prediction item was pinned")
+        LAUNCHER_HOTSEAT_PREDICTION_PINNED(629);
 
         // ADD MORE
 
@@ -401,8 +453,8 @@
     }
 
     /**
-     * Logs snapshot, or impression of the current workspace.
+     * Logs impression of the current workspace with additional launcher events.
      */
-    public void logSnapshot() {
+    public void logSnapshot(List<EventEnum> additionalEvents) {
     }
 }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index e094cab..a40cc26 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -19,26 +19,21 @@
 import static com.android.launcher3.logging.LoggerUtils.newAction;
 import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.logging.LoggerUtils.newControlTarget;
 import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
 import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 
 import static java.util.Optional.ofNullable;
 
-import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.os.Process;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 
@@ -54,7 +49,6 @@
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -101,7 +95,7 @@
      *
      * @return whether container data was added.
      */
-    public boolean fillLogContainer(@Nullable View v, Target child,
+    private boolean fillLogContainer(@Nullable View v, Target child,
             @Nullable ArrayList<Target> targets) {
         LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
         if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
@@ -125,55 +119,6 @@
     private boolean mAppOrTaskLaunch;
     private boolean mPreviousHomeGesture;
 
-    //                      APP_ICON    SHORTCUT    WIDGET
-    // --------------------------------------------------------------
-    // packageNameHash      required    optional    required
-    // componentNameHash    required                required
-    // intentHash                       required
-    // --------------------------------------------------------------
-
-    @Deprecated
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        Action action = newTouchAction(Action.Touch.TAP);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-        if (fillLogContainer(v, itemTarget, targets)) {
-            onFillInLogContainerData((ItemInfo) v.getTag(), itemTarget, targets);
-            fillIntentInfo(itemTarget, intent, userHandle);
-        }
-        LauncherEvent event = newLauncherEvent(action,  targets);
-        dispatchUserEvent(event, intent);
-        mAppOrTaskLaunch = true;
-    }
-
-    /**
-     * Dummy method.
-     */
-    public void logActionTip(int actionType, int viewType) {
-    }
-
-    @Deprecated
-    public void logTaskLaunchOrDismiss(int action, int direction, int taskIndex,
-            ComponentKey componentKey) {
-        LauncherEvent event = newLauncherEvent(newTouchAction(action), // TAP or SWIPE or FLING
-                newTarget(Target.Type.ITEM));
-        if (action == Action.Touch.SWIPE || action == Action.Touch.FLING) {
-            // Direction DOWN means the task was launched, UP means it was dismissed.
-            event.action.dir = direction;
-        }
-        event.srcTarget[0].itemType = ItemType.TASK;
-        event.srcTarget[0].pageIndex = taskIndex;
-        fillComponentInfo(event.srcTarget[0], componentKey.componentName);
-        dispatchUserEvent(event, null);
-        mAppOrTaskLaunch = true;
-    }
-
-    protected void fillIntentInfo(Target target, Intent intent, @Nullable UserHandle userHandle) {
-        target.intentHash = intent.hashCode();
-        target.isWorkApp = userHandle != null && !userHandle.equals(Process.myUserHandle());
-        fillComponentInfo(target, intent.getComponent());
-    }
-
     private void fillComponentInfo(Target target, ComponentName cn) {
         if (cn != null) {
             target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
@@ -181,22 +126,6 @@
         }
     }
 
-    public void logNotificationLaunch(View v, PendingIntent intent) {
-        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.TAP),
-                newItemTarget(v, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-        Target itemTarget = newItemTarget(v, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(v, itemTarget, targets)) {
-            itemTarget.packageNameHash = (mUuidStr + intent.getCreatorPackage()).hashCode();
-        }
-        dispatchUserEvent(event, null);
-    }
-
-    public void logActionCommand(int command, Target srcTarget) {
-        logActionCommand(command, srcTarget, null);
-    }
-
     public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
         logActionCommand(command, newContainerTarget(srcContainerType),
                 dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
@@ -227,25 +156,6 @@
         dispatchUserEvent(event, null);
     }
 
-    /**
-     * TODO: Make this function work when a container view is passed as the 2nd param.
-     */
-    public void logActionCommand(int command, View itemView, int srcContainerType) {
-        LauncherEvent event = newLauncherEvent(newCommandAction(command),
-                newItemTarget(itemView, mInstantAppResolver), newTarget(Target.Type.CONTAINER));
-
-        Target itemTarget = newItemTarget(itemView, mInstantAppResolver);
-        ArrayList<Target> targets = makeTargetsList(itemTarget);
-
-        if (fillLogContainer(itemView, itemTarget, targets)) {
-            // TODO: Remove the following two lines once fillInLogContainerData can take in a
-            // container view.
-            itemTarget.type = Target.Type.CONTAINER;
-            itemTarget.containerType = srcContainerType;
-        }
-        dispatchUserEvent(event, null);
-    }
-
     public void logActionOnControl(int action, int controlType) {
         logActionOnControl(action, controlType, null);
     }
@@ -332,7 +242,6 @@
         event.srcTarget[0].spanX = downX;
         event.srcTarget[0].spanY = downY;
         dispatchUserEvent(event, null);
-        resetElapsedContainerMillis("state changed");
     }
 
     public void logActionOnItem(int action, int dir, int itemType) {
@@ -386,7 +295,6 @@
         ArrayList<Target> targets = makeTargetsList(child);
         fillLogContainer(icon, child, targets);
         dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
-        resetElapsedContainerMillis("deep shortcut open");
     }
 
     public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
@@ -397,7 +305,7 @@
         Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
         ArrayList<Target> destTargets = makeTargetsList(destChild);
 
-        dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
+        //dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
         if (dropTargetAsView instanceof LogContainerProvider) {
             ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
                     destChild, destTargets);
@@ -414,35 +322,6 @@
         dispatchUserEvent(event, null);
     }
 
-    public void logActionBack(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft, int containerType) {
-        int actionTouch = isButton ? Action.Touch.TAP : Action.Touch.SWIPE;
-        Action action = newCommandAction(actionTouch);
-        action.command = Action.Command.BACK;
-        action.dir = isButton ? Action.Direction.NONE :
-                gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
-                ControlType.BACK_GESTURE);
-        target.spanX = downX;
-        target.spanY = downY;
-        target.cardinality = completed ? 1 : 0;
-        LauncherEvent event = newLauncherEvent(action, target, newContainerTarget(containerType));
-
-        dispatchUserEvent(event, null);
-    }
-
-    /**
-     * Currently logs following containers: workspace, allapps, widget tray.
-     */
-    public final void resetElapsedContainerMillis(String reason) {
-        mElapsedContainerMillis = SystemClock.uptimeMillis();
-        if (!IS_VERBOSE) {
-            return;
-        }
-        Log.d(TAG, "resetElapsedContainerMillis reason=" + reason);
-
-    }
-
     public final void startSession() {
         mSessionStarted = true;
         mElapsedSessionMillis = SystemClock.uptimeMillis();
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index eb5d106..2695e66 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -305,7 +305,7 @@
      *
      * @return the corresponding AppInfo or null
      */
-    private @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
+    public @Nullable AppInfo findAppInfo(@NonNull ComponentName componentName,
                                           @NonNull UserHandle user) {
         for (AppInfo info: data) {
             if (componentName.equals(info.componentName) && user.equals(info.user)) {
diff --git a/src/com/android/launcher3/model/AppLaunchTracker.java b/src/com/android/launcher3/model/AppLaunchTracker.java
deleted file mode 100644
index a93c0dd..0000000
--- a/src/com/android/launcher3/model/AppLaunchTracker.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.model;
-
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-/**
- * Callback for receiving various app launch events
- */
-public class AppLaunchTracker implements ResourceBasedOverride {
-
-    public static final MainThreadInitializedObject<AppLaunchTracker> INSTANCE =
-            forOverride(AppLaunchTracker.class, R.string.app_launch_tracker_class);
-
-    public void onReturnedToHome() { }
-}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 8b0ef7b..5c85bab 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
 import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
 
 import android.util.Log;
@@ -27,6 +26,7 @@
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -76,18 +76,20 @@
         ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
         ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
         final IntArray orderedScreenIds = new IntArray();
+        ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
 
         synchronized (mBgDataModel) {
             workspaceItems.addAll(mBgDataModel.workspaceItems);
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+            mBgDataModel.extraItems.forEach(extraItems::add);
             mBgDataModel.lastBindId++;
             mMyBindingId = mBgDataModel.lastBindId;
         }
 
         for (Callbacks cb : mCallbacksList) {
             new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
-                    workspaceItems, appWidgets, orderedScreenIds).bind();
+                    workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
         }
     }
 
@@ -135,7 +137,7 @@
         private final ArrayList<ItemInfo> mWorkspaceItems;
         private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
         private final IntArray mOrderedScreenIds;
-
+        private final ArrayList<FixedContainerItems> mExtraItems;
 
         WorkspaceBinder(Callbacks callbacks,
                 Executor uiExecutor,
@@ -144,6 +146,7 @@
                 int myBindingId,
                 ArrayList<ItemInfo> workspaceItems,
                 ArrayList<LauncherAppWidgetInfo> appWidgets,
+                ArrayList<FixedContainerItems> extraItems,
                 IntArray orderedScreenIds) {
             mCallbacks = callbacks;
             mUiExecutor = uiExecutor;
@@ -152,6 +155,7 @@
             mMyBindingId = myBindingId;
             mWorkspaceItems = workspaceItems;
             mAppWidgets = appWidgets;
+            mExtraItems = extraItems;
             mOrderedScreenIds = orderedScreenIds;
         }
 
@@ -198,15 +202,15 @@
             // Load items on the current page.
             bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
             bindAppWidgets(currentAppWidgets, mainExecutor);
+            mExtraItems.forEach(item ->
+                    executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
 
-            // Locate available spots for prediction using currentWorkspaceItems
-            IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
-            bindPredictedItems(gaps, mainExecutor);
             // In case of validFirstPage, only bind the first screen, and defer binding the
             // remaining screens after first onDraw (and an optional the fade animation whichever
             // happens later).
             // This ensures that the first screen is immediately visible (eg. during rotation)
             // In case of !validFirstPage, bind all pages one after other.
+
             final Executor deferredExecutor =
                     validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
 
@@ -253,11 +257,6 @@
             }
         }
 
-        private void bindPredictedItems(IntArray ranks, final Executor executor) {
-            ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems);
-            executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor);
-        }
-
         protected void executeCallbacksTask(CallbackTask task, Executor executor) {
             executor.execute(() -> {
                 if (mMyBindingId != mBgDataModel.lastBindId) {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 9013cba..d1e5017 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -22,7 +22,9 @@
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -30,7 +32,10 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
 
 /**
  * Extension of {@link ModelUpdateTask} with some utility methods
@@ -88,11 +93,27 @@
         return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
     }
 
-
-    public void bindUpdatedWorkspaceItems(final ArrayList<WorkspaceItemInfo> updatedShortcuts) {
-        if (!updatedShortcuts.isEmpty()) {
-            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(updatedShortcuts));
+    public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
+        // Bind workspace items
+        List<WorkspaceItemInfo> workspaceUpdates = allUpdates.stream()
+                .filter(info -> info.id != ItemInfo.NO_ID)
+                .collect(Collectors.toList());
+        if (!workspaceUpdates.isEmpty()) {
+            scheduleCallbackTask(c -> c.bindWorkspaceItemsChanged(workspaceUpdates));
         }
+
+        // Bind extra items if any
+        allUpdates.stream()
+                .mapToInt(info -> info.container)
+                .distinct()
+                .mapToObj(mDataModel.extraItems::get)
+                .filter(Objects::nonNull)
+                .forEach(this::bindExtraContainerItems);
+    }
+
+    public void bindExtraContainerItems(FixedContainerItems item) {
+        FixedContainerItems copy = item.clone();
+        scheduleCallbackTask(c -> c.bindExtraContainerItems(copy));
     }
 
     public void bindDeepShortcuts(BgDataModel dataModel) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7524920..49b40ed 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -15,19 +15,24 @@
  */
 package com.android.launcher3.model;
 
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+
 import static com.android.launcher3.model.WidgetsModel.GO_DISABLE_WIDGETS;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+
 import android.content.Context;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArraySet;
 import android.util.Log;
-import android.util.MutableInt;
 
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
@@ -36,8 +41,10 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -50,14 +57,16 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.function.BiConsumer;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * All the data stored in-memory and managed by the LauncherModel
@@ -89,21 +98,9 @@
     public final IntSparseArrayMap<FolderInfo> folders = new IntSparseArrayMap<>();
 
     /**
-     * Map of ShortcutKey to the number of times it is pinned.
+     * Extra container based items
      */
-    public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
-
-    /**
-     * List of all cached predicted items visible on home screen
-     */
-    public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
-
-    /**
-     * @see Callbacks#FLAG_HAS_SHORTCUT_PERMISSION
-     * @see Callbacks#FLAG_QUIET_MODE_ENABLED
-     * @see Callbacks#FLAG_QUIET_MODE_CHANGE_PERMISSION
-     */
-    public int flags;
+    public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
 
     /**
      * Maps all launcher activities to counts of their shortcuts.
@@ -128,8 +125,8 @@
         appWidgets.clear();
         folders.clear();
         itemsIdMap.clear();
-        pinnedShortcutCounts.clear();
         deepShortcutMap.clear();
+        extraItems.clear();
     }
 
     /**
@@ -182,6 +179,7 @@
     }
 
     public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
+        ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
         for (ItemInfo item : items) {
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
@@ -200,14 +198,7 @@
                     workspaceItems.remove(item);
                     break;
                 case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                    // Decrement pinned shortcut count
-                    ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
-                    MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
-                    if ((count == null || --count.value == 0)
-                            && !InstallShortcutReceiver.getPendingShortcuts(context)
-                                .contains(pinnedShortcut)) {
-                        unpinShortcut(context, pinnedShortcut);
-                    }
+                    updatedDeepShortcuts.add(item.user);
                     // Fall through.
                 }
                 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
@@ -221,6 +212,7 @@
             }
             itemsIdMap.remove(item.id);
         }
+        updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
@@ -230,23 +222,7 @@
                 folders.put(item.id, (FolderInfo) item);
                 workspaceItems.add(item);
                 break;
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                // Increment the count for the given shortcut
-                ShortcutKey pinnedShortcut = ShortcutKey.fromItemInfo(item);
-                MutableInt count = pinnedShortcutCounts.get(pinnedShortcut);
-                if (count == null) {
-                    count = new MutableInt(1);
-                    pinnedShortcutCounts.put(pinnedShortcut, count);
-                } else {
-                    count.value++;
-                }
-
-                // Since this is a new item, pin the shortcut in the system server.
-                if (newItem && count.value == 1) {
-                    updatePinnedShortcuts(context, pinnedShortcut, List::add);
-                }
-                // Fall through
-            }
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                 if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
@@ -271,36 +247,86 @@
                 appWidgets.add((LauncherAppWidgetInfo) item);
                 break;
         }
+        if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+            updateShortcutPinnedState(context, item.user);
+        }
     }
 
     /**
-     * Removes the given shortcut from the current list of pinned shortcuts.
-     * (Runs on background thread)
+     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * shortcuts and unpinning any extra shortcuts.
      */
-    public void unpinShortcut(Context context, ShortcutKey key) {
-        updatePinnedShortcuts(context, key, List::remove);
+    public void updateShortcutPinnedState(Context context) {
+        for (UserHandle user : UserCache.INSTANCE.get(context).getUserProfiles()) {
+            updateShortcutPinnedState(context, user);
+        }
     }
 
-    private void updatePinnedShortcuts(Context context, ShortcutKey key,
-            BiConsumer<List<String>, String> idOp) {
+    /**
+     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * shortcuts and unpinning any extra shortcuts.
+     */
+    public synchronized void updateShortcutPinnedState(Context context, UserHandle user) {
         if (GO_DISABLE_WIDGETS) {
             return;
         }
-        String packageName = key.componentName.getPackageName();
-        String id = key.getId();
-        UserHandle user = key.user;
-        List<String> pinnedIds = new ShortcutRequest(context, user)
-                .forPackage(packageName)
-                .query(PINNED)
-                .stream()
-                .map(ShortcutInfo::getId)
-                .collect(Collectors.toCollection(ArrayList::new));
-        idOp.accept(pinnedIds, id);
-        try {
-            context.getSystemService(LauncherApps.class).pinShortcuts(packageName, pinnedIds, user);
-        } catch (SecurityException | IllegalStateException e) {
-            Log.w(TAG, "Failed to pin shortcut", e);
+
+        // Collect all system shortcuts
+        QueryResult result = new ShortcutRequest(context, user)
+                .query(PINNED | FLAG_GET_KEY_FIELDS_ONLY);
+        if (!result.wasSuccess()) {
+            return;
         }
+        // Map of packageName to shortcutIds that are currently in the system
+        Map<String, Set<String>> systemMap = result.stream()
+                .collect(groupingBy(ShortcutInfo::getPackage,
+                        mapping(ShortcutInfo::getId, Collectors.toSet())));
+
+        // Collect all model shortcuts
+        Stream.Builder<WorkspaceItemInfo> itemStream = Stream.builder();
+        forAllWorkspaceItemInfos(user, itemStream::accept);
+        // Map of packageName to shortcutIds that are currently in our model
+        Map<String, Set<String>> modelMap = Stream.concat(
+                    // Model shortcuts
+                    itemStream.build()
+                        .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        .map(ShortcutKey::fromItemInfo),
+                    // Pending shortcuts
+                    ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
+                .collect(groupingBy(ShortcutKey::getPackageName,
+                        mapping(ShortcutKey::getId, Collectors.toSet())));
+
+        // Check for diff
+        for (Map.Entry<String, Set<String>> entry : modelMap.entrySet()) {
+            Set<String> modelShortcuts = entry.getValue();
+            Set<String> systemShortcuts = systemMap.remove(entry.getKey());
+            if (systemShortcuts == null) {
+                systemShortcuts = Collections.emptySet();
+            }
+
+            // Do not use .equals as it can vary based on the type of set
+            if (systemShortcuts.size() != modelShortcuts.size()
+                    || !systemShortcuts.containsAll(modelShortcuts)) {
+                // Update system state for this package
+                try {
+                    context.getSystemService(LauncherApps.class).pinShortcuts(
+                            entry.getKey(), new ArrayList<>(modelShortcuts), user);
+                } catch (SecurityException | IllegalStateException e) {
+                    Log.w(TAG, "Failed to pin shortcut", e);
+                }
+            }
+        }
+
+        // If there are any extra pinned shortcuts, remove them
+        systemMap.keySet().forEach(packageName -> {
+            // Update system state
+            try {
+                context.getSystemService(LauncherApps.class).pinShortcuts(
+                        packageName, Collections.emptyList(), user);
+            } catch (SecurityException | IllegalStateException e) {
+                Log.w(TAG, "Failed to unpin shortcut", e);
+            }
+        });
     }
 
     /**
@@ -360,8 +386,48 @@
                 op.accept((WorkspaceItemInfo) info);
             }
         }
+
+        for (int i = extraItems.size() - 1; i >= 0; i--) {
+            for (ItemInfo info : extraItems.valueAt(i).items) {
+                if (info instanceof WorkspaceItemInfo && userHandle.equals(info.user)) {
+                    op.accept((WorkspaceItemInfo) info);
+                }
+            }
+        }
     }
 
+    /**
+     * An object containing items corresponding to a fixed container
+     */
+    public static class FixedContainerItems {
+
+        public final int containerId;
+        public final List<ItemInfo> items;
+
+        public FixedContainerItems(int containerId) {
+            this(containerId, new ArrayList<>());
+        }
+
+        public FixedContainerItems(int containerId, List<ItemInfo> items) {
+            this.containerId = containerId;
+            this.items = items;
+        }
+
+        @Override
+        public FixedContainerItems clone() {
+            return new FixedContainerItems(containerId, new ArrayList<>(items));
+        }
+
+        public void setItems(List<ItemInfo> newItems) {
+            items.clear();
+            newItems.forEach(item -> {
+                item.container = containerId;
+                items.add(item);
+            });
+        }
+    }
+
+
     public interface Callbacks {
         // If the launcher has permission to access deep shortcuts.
         int FLAG_HAS_SHORTCUT_PERMISSION = 1 << 0;
@@ -384,7 +450,7 @@
         void bindAppsAdded(IntArray newScreens,
                 ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated);
         void bindPromiseAppProgressUpdated(PromiseAppInfo app);
-        void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated);
+        void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated);
         void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
         void bindRestoreItemsChange(HashSet<ItemInfo> updates);
         void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher);
@@ -393,11 +459,11 @@
         void executeOnNextDraw(ViewOnDrawExecutor executor);
         void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
 
-        void bindAllApplications(AppInfo[] apps, int flags);
-
         /**
-         * Binds predicted appInfos at at available prediction slots.
+         * Binds extra item provided any external source
          */
-        void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
+        default void bindExtraContainerItems(FixedContainerItems item) { }
+
+        void bindAllApplications(AppInfo[] apps, int flags);
     }
 }
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index 5112304..70d1b48 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -17,25 +17,30 @@
 
 import static android.os.Process.myUserHandle;
 
+import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.mapping;
+
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller.SessionInfo;
+import android.os.UserHandle;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Helper class to send broadcasts to package installers that have:
@@ -61,26 +66,10 @@
 
     private static final String VERIFICATION_TOKEN_EXTRA = "verificationToken";
 
-    private final MultiHashMap<String, String> mPackagesForInstaller;
+    private final HashMap<PackageUserKey, SessionInfo> mSessionInfoForPackage;
 
     public FirstScreenBroadcast(HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
-        mPackagesForInstaller = getPackagesForInstaller(sessionInfoForPackage);
-    }
-
-    /**
-     * @return Map where the key is the package name of the installer, and the value is a list
-     *         of packages with active sessions for that installer.
-     */
-    private MultiHashMap<String, String> getPackagesForInstaller(
-            HashMap<PackageUserKey, SessionInfo> sessionInfoForPackage) {
-        MultiHashMap<String, String> packagesForInstaller = new MultiHashMap<>();
-        for (Map.Entry<PackageUserKey, SessionInfo> entry : sessionInfoForPackage.entrySet()) {
-            if (myUserHandle().equals(entry.getKey().mUser)) {
-                packagesForInstaller.addToList(entry.getValue().getInstallerPackageName(),
-                        entry.getKey().mPackageName);
-            }
-        }
-        return packagesForInstaller;
+        mSessionInfoForPackage = sessionInfoForPackage;
     }
 
     /**
@@ -88,9 +77,15 @@
      * first screen.
      */
     public void sendBroadcasts(Context context, List<ItemInfo> firstScreenItems) {
-        for (Map.Entry<String, ArrayList<String>> entry : mPackagesForInstaller.entrySet()) {
-            sendBroadcastToInstaller(context, entry.getKey(), entry.getValue(), firstScreenItems);
-        }
+        UserHandle myUser = myUserHandle();
+        mSessionInfoForPackage
+                .values()
+                .stream()
+                .filter(info -> myUser.equals(getUserHandle(info)))
+                .collect(groupingBy(SessionInfo::getInstallerPackageName,
+                        mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
+                .forEach((installer, packages) ->
+                    sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
     }
 
     /**
@@ -99,7 +94,7 @@
      * @param firstScreenItems List of items on the first screen.
      */
     private void sendBroadcastToInstaller(Context context, String installerPackageName,
-            List<String> packages, List<ItemInfo> firstScreenItems) {
+            Set<String> packages, List<ItemInfo> firstScreenItems) {
         Set<String> folderItems = new HashSet<>();
         Set<String> workspaceItems = new HashSet<>();
         Set<String> hotseatItems = new HashSet<>();
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
new file mode 100644
index 0000000..5e48a0f
--- /dev/null
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2008 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 android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
+
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings.Favorites;
+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.shortcuts.ShortcutKey;
+import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PersistedItemArray;
+import com.android.launcher3.util.Preconditions;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Class to maintain a queue of pending items to be added to the workspace.
+ */
+public class ItemInstallQueue {
+
+    public static final int FLAG_ACTIVITY_PAUSED = 1;
+    public static final int FLAG_LOADER_RUNNING = 2;
+    public static final int FLAG_DRAG_AND_DROP = 4;
+
+    private static final String TAG = "InstallShortcutReceiver";
+
+    // The set of shortcuts that are pending install
+    private static final String APPS_PENDING_INSTALL = "apps_to_install";
+
+    public static final int NEW_SHORTCUT_BOUNCE_DURATION = 450;
+    public static final int NEW_SHORTCUT_STAGGER_DELAY = 85;
+
+    public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
+            new MainThreadInitializedObject<>(ItemInstallQueue::new);
+
+    private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
+            new PersistedItemArray<>(APPS_PENDING_INSTALL);
+    private final Context mContext;
+
+    // Determines whether to defer installing shortcuts immediately until
+    // processAllPendingInstalls() is called.
+    private int mInstallQueueDisabledFlags = 0;
+
+    // Only accessed on worker thread
+    private List<PendingInstallShortcutInfo> mItems;
+
+    private ItemInstallQueue(Context context) {
+        mContext = context;
+    }
+
+    @WorkerThread
+    private void ensureQueueLoaded() {
+        Preconditions.assertWorkerThread();
+        if (mItems == null) {
+            mItems = mStorage.read(mContext, this::decode);
+        }
+    }
+
+    @WorkerThread
+    private void addToQueue(PendingInstallShortcutInfo info) {
+        ensureQueueLoaded();
+        mItems.add(info);
+        mStorage.write(mContext, mItems);
+    }
+
+    @WorkerThread
+    private void flushQueueInBackground() {
+        Launcher launcher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+        if (launcher == null) {
+            // Launcher not loaded
+            return;
+        }
+        ensureQueueLoaded();
+        if (mItems.isEmpty()) {
+            return;
+        }
+
+        List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
+                .map(info -> info.getItemInfo(mContext))
+                .collect(Collectors.toList());
+
+        // Add the items and clear queue
+        if (!installQueue.isEmpty()) {
+            launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
+        }
+        mItems.clear();
+        mStorage.getFile(mContext).delete();
+    }
+
+    /**
+     * Removes previously added items from the queue.
+     */
+    @WorkerThread
+    public void removeFromInstallQueue(HashSet<String> packageNames, UserHandle user) {
+        if (packageNames.isEmpty()) {
+            return;
+        }
+        ensureQueueLoaded();
+        if (mItems.removeIf(item ->
+                item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
+            mStorage.write(mContext, mItems);
+        }
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(ShortcutInfo info) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info));
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(AppWidgetProviderInfo info, int widgetId) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(info, widgetId));
+    }
+
+    /**
+     * Adds an item to the install queue
+     */
+    public void queueItem(String packageName, UserHandle userHandle) {
+        queuePendingShortcutInfo(new PendingInstallShortcutInfo(packageName, userHandle));
+    }
+
+    /**
+     * Returns a stream of all pending shortcuts in the queue
+     */
+    @WorkerThread
+    public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
+        ensureQueueLoaded();
+        return mItems.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
+                .map(item -> ShortcutKey.fromIntent(item.intent, user));
+    }
+
+    private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
+        // Queue the item up for adding if launcher has not loaded properly yet
+        MODEL_EXECUTOR.post(() -> addToQueue(info));
+        flushInstallQueue();
+    }
+
+    /**
+     * Pauses the push-to-model flow until unpaused. All items are held in the queue and
+     * not added to the model.
+     */
+    public void pauseModelPush(int flag) {
+        mInstallQueueDisabledFlags |= flag;
+    }
+
+    /**
+     * Adds all the queue items to the model if the use is completely resumed.
+     */
+    public void resumeModelPush(int flag) {
+        mInstallQueueDisabledFlags &= ~flag;
+        flushInstallQueue();
+    }
+
+    private void flushInstallQueue() {
+        if (mInstallQueueDisabledFlags != 0) {
+            return;
+        }
+        MODEL_EXECUTOR.post(this::flushQueueInBackground);
+    }
+
+    private static class PendingInstallShortcutInfo extends ItemInfo {
+
+        final Intent intent;
+
+        @Nullable ShortcutInfo shortcutInfo;
+        @Nullable AppWidgetProviderInfo providerInfo;
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a pending launcher target.
+         */
+        public PendingInstallShortcutInfo(String packageName, UserHandle userHandle) {
+            itemType = Favorites.ITEM_TYPE_APPLICATION;
+            intent = new Intent().setPackage(packageName);
+            user = userHandle;
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent a deep shortcut.
+         */
+        public PendingInstallShortcutInfo(ShortcutInfo info) {
+            itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+            intent = ShortcutKey.makeIntent(info);
+            user = info.getUserHandle();
+
+            shortcutInfo = info;
+        }
+
+        /**
+         * Initializes a PendingInstallShortcutInfo to represent an app widget.
+         */
+        public PendingInstallShortcutInfo(AppWidgetProviderInfo info, int widgetId) {
+            itemType = Favorites.ITEM_TYPE_APPWIDGET;
+            intent = new Intent()
+                    .setComponent(info.provider)
+                    .putExtra(EXTRA_APPWIDGET_ID, widgetId);
+            user = info.getProfile();
+
+            providerInfo = info;
+        }
+
+        @Override
+        public Intent getIntent() {
+            return intent;
+        }
+
+        public Pair<ItemInfo, Object> getItemInfo(Context context) {
+            switch (itemType) {
+                case ITEM_TYPE_APPLICATION: {
+                    String packageName = intent.getPackage();
+                    List<LauncherActivityInfo> laiList =
+                            context.getSystemService(LauncherApps.class)
+                                    .getActivityList(packageName, user);
+
+                    final WorkspaceItemInfo si = new WorkspaceItemInfo();
+                    si.user = user;
+                    si.itemType = ITEM_TYPE_APPLICATION;
+
+                    LauncherActivityInfo lai;
+                    boolean usePackageIcon = laiList.isEmpty();
+                    if (usePackageIcon) {
+                        lai = null;
+                        si.intent = makeLaunchIntent(new ComponentName(packageName, ""))
+                                .setPackage(packageName);
+                        si.status |= WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
+                    } else {
+                        lai = laiList.get(0);
+                        si.intent = makeLaunchIntent(lai);
+                    }
+                    LauncherAppState.getInstance(context).getIconCache()
+                            .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
+                    return Pair.create(si, null);
+                }
+                case ITEM_TYPE_DEEP_SHORTCUT: {
+                    WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, context);
+                    LauncherAppState.getInstance(context).getIconCache()
+                            .getShortcutIcon(itemInfo, shortcutInfo);
+                    return Pair.create(itemInfo, shortcutInfo);
+                }
+                case ITEM_TYPE_APPWIDGET: {
+                    LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
+                            .fromProviderInfo(context, providerInfo);
+                    LauncherAppWidgetInfo widgetInfo = new LauncherAppWidgetInfo(
+                            intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, 0),
+                            info.provider);
+                    InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+                    widgetInfo.minSpanX = info.minSpanX;
+                    widgetInfo.minSpanY = info.minSpanY;
+                    widgetInfo.spanX = Math.min(info.spanX, idp.numColumns);
+                    widgetInfo.spanY = Math.min(info.spanY, idp.numRows);
+                    widgetInfo.user = user;
+                    return Pair.create(widgetInfo, providerInfo);
+                }
+            }
+            return null;
+        }
+    }
+
+    private static String getIntentPackage(Intent intent) {
+        return intent.getComponent() == null
+                ? intent.getPackage() : intent.getComponent().getPackageName();
+    }
+
+    private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
+        switch (itemType) {
+            case Favorites.ITEM_TYPE_APPLICATION:
+                return new PendingInstallShortcutInfo(intent.getPackage(), user);
+            case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+                List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
+                        .buildRequest(mContext)
+                        .query(ShortcutRequest.ALL);
+                if (si.isEmpty()) {
+                    return null;
+                } else {
+                    return new PendingInstallShortcutInfo(si.get(0));
+                }
+            }
+            case Favorites.ITEM_TYPE_APPWIDGET: {
+                int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+                AppWidgetProviderInfo info =
+                        AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
+                if (info == null || !info.provider.equals(intent.getComponent())
+                        || !info.getProfile().equals(user)) {
+                    return null;
+                }
+                return new PendingInstallShortcutInfo(info, widgetId);
+            }
+            default:
+                Log.e(TAG, "Unknown item type");
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 165d1ea..a27ac23 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -65,7 +65,7 @@
 
     private static final String TAG = "LoaderCursor";
 
-    public final LongSparseArray<UserHandle> allUsers;
+    private final LongSparseArray<UserHandle> allUsers;
 
     private final Uri mContentUri;
     private final Context mContext;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4a64522..2b4520b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -46,12 +46,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.MutableInt;
 import android.util.TimingLogger;
 
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherSettings;
@@ -85,7 +81,6 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.LooperIdleLock;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.TraceHelper;
@@ -97,6 +92,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CancellationException;
 
 /**
@@ -112,6 +108,7 @@
     protected final LauncherAppState mApp;
     private final AllAppsList mBgAllAppsList;
     protected final BgDataModel mBgDataModel;
+    private final ModelDelegate mModelDelegate;
 
     private FirstScreenBroadcast mFirstScreenBroadcast;
 
@@ -130,11 +127,15 @@
 
     private boolean mStopped;
 
+    private final Set<PackageUserKey> mPendingPackages = new HashSet<>();
+    private boolean mItemsDeleted = false;
+
     public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
-            LoaderResults results) {
+            ModelDelegate modelDelegate, LoaderResults results) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
         mBgDataModel = dataModel;
+        mModelDelegate = modelDelegate;
         mResults = results;
 
         mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
@@ -189,13 +190,13 @@
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts);
-            loadCachedPredictions();
             logger.addSplit("loadWorkspace");
 
             verifyNotStopped();
             mResults.bindWorkspace();
             logger.addSplit("bindWorkspace");
 
+            mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
             logger.addSplit("sendFirstScreenActiveInstallsBroadcast");
@@ -272,6 +273,7 @@
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 loadFolderNames();
             }
+            sanitizeData();
 
             verifyNotStopped();
             updateHandler.finish();
@@ -293,16 +295,17 @@
     }
 
     private void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
-        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
+        loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI,
+                null /* selection */);
     }
 
-    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri) {
+    protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri,
+            String selection) {
         final Context context = mApp.getContext();
         final ContentResolver contentResolver = context.getContentResolver();
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
         final boolean isSafeMode = pmHelper.isSafeMode();
         final boolean isSdCardReady = Utilities.isBootCompleted();
-        final MultiHashMap<UserHandle, String> pendingPackages = new MultiHashMap<>();
 
         boolean clearDb = false;
         try {
@@ -331,6 +334,7 @@
 
         synchronized (mBgDataModel) {
             mBgDataModel.clear();
+            mPendingPackages.clear();
 
             final HashMap<PackageUserKey, SessionInfo> installingPkgs =
                     mSessionHelper.getActiveSessions();
@@ -341,8 +345,8 @@
 
             Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
             final LoaderCursor c = new LoaderCursor(
-                    contentResolver.query(contentUri, null, null, null, null), contentUri, mApp,
-                    mUserManagerState);
+                    contentResolver.query(contentUri, null, selection, null, null), contentUri,
+                    mApp, mUserManagerState);
 
             try {
                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
@@ -358,15 +362,12 @@
                 final int optionsIndex = c.getColumnIndexOrThrow(
                         LauncherSettings.Favorites.OPTIONS);
 
-                final LongSparseArray<UserHandle> allUsers = c.allUsers;
                 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
 
                 mUserManagerState.init(mUserCache, mUserManager);
 
                 for (UserHandle user : mUserCache.getUserProfiles()) {
                     long serialNo = mUserCache.getSerialNumberForUser(user);
-                    allUsers.put(serialNo, user);
-
                     boolean userUnlocked = mUserManager.isUserUnlocked(user);
 
                     // We can only query for shortcuts when the user is unlocked.
@@ -417,16 +418,6 @@
                             ComponentName cn = intent.getComponent();
                             targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
 
-                            if (allUsers.indexOfValue(c.user) < 0) {
-                                if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-                                    c.markDeleted("Legacy shortcuts are only allowed for current users");
-                                    continue;
-                                } else if (c.restoreFlag != 0) {
-                                    // Don't restore items for other profiles.
-                                    c.markDeleted("Restore from other profiles not supported");
-                                    continue;
-                                }
-                            }
                             if (TextUtils.isEmpty(targetPkg) &&
                                     c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
                                 c.markDeleted("Only legacy shortcuts can have null package");
@@ -496,7 +487,7 @@
                                     // SdCard is not ready yet. Package might get available,
                                     // once it is ready.
                                     Log.d(TAG, "Missing pkg, will check later: " + targetPkg);
-                                    pendingPackages.addToList(c.user, targetPkg);
+                                    mPendingPackages.add(new PackageUserKey(targetPkg, c.user));
                                     // Add the icon on the workspace anyway.
                                     allowMissingTarget = true;
                                 } else {
@@ -770,6 +761,9 @@
                 IOUtils.closeSilently(c);
             }
 
+            // Load delegate items
+            mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
+
             // Break early if we've stopped loading
             if (mStopped) {
                 mBgDataModel.clear();
@@ -777,34 +771,7 @@
             }
 
             // Remove dead items
-            if (c.commitDeleted()) {
-                // Remove any empty folder
-                int[] deletedFolderIds = LauncherSettings.Settings
-                        .call(contentResolver,
-                                LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
-                        .getIntArray(LauncherSettings.Settings.EXTRA_VALUE);
-                for (int folderId : deletedFolderIds) {
-                    mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
-                    mBgDataModel.folders.remove(folderId);
-                    mBgDataModel.itemsIdMap.remove(folderId);
-                }
-
-                // Remove any ghost widgets
-                LauncherSettings.Settings.call(contentResolver,
-                        LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
-            }
-
-            // Unpin shortcuts that don't exist on the workspace.
-            HashSet<ShortcutKey> pendingShortcuts =
-                    InstallShortcutReceiver.getPendingShortcuts(context);
-            for (ShortcutKey key : shortcutKeyToPinnedShortcuts.keySet()) {
-                MutableInt numTimesPinned = mBgDataModel.pinnedShortcutCounts.get(key);
-                if ((numTimesPinned == null || numTimesPinned.value == 0)
-                        && !pendingShortcuts.contains(key)) {
-                    // Shortcut is pinned but doesn't exist on the workspace; unpin it.
-                    mBgDataModel.unpinShortcut(context, key);
-                }
-            }
+            mItemsDeleted = c.commitDeleted();
 
             // Sort the folder items, update ranks, and make sure all preview items are high res.
             FolderGridOrganizer verifier =
@@ -830,13 +797,6 @@
             }
 
             c.commitRestoredItems();
-            if (!isSdCardReady && !pendingPackages.isEmpty()) {
-                context.registerReceiver(
-                        new SdCardAvailableReceiver(mApp, pendingPackages),
-                        new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
-                        null,
-                        MODEL_EXECUTOR.getHandler());
-            }
         }
     }
 
@@ -861,21 +821,35 @@
         }
     }
 
-    @WorkerThread
-    private void loadCachedPredictions() {
-        synchronized (mBgDataModel) {
-            List<ComponentKey> componentKeys =
-                    mApp.getPredictionModel().getPredictionComponentKeys();
-            List<LauncherActivityInfo> l;
-            mBgDataModel.cachedPredictedItems.clear();
-            for (ComponentKey key : componentKeys) {
-                l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
-                if (l.size() == 0) continue;
-                AppInfo info = new AppInfo(l.get(0), key.user,
-                        mUserManagerState.isUserQuiet(key.user));
-                mBgDataModel.cachedPredictedItems.add(info);
-                mIconCache.getTitleAndIcon(info, false);
+    private void sanitizeData() {
+        Context context = mApp.getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        if (mItemsDeleted) {
+            // Remove any empty folder
+            int[] deletedFolderIds = LauncherSettings.Settings
+                    .call(contentResolver,
+                            LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS)
+                    .getIntArray(LauncherSettings.Settings.EXTRA_VALUE);
+            for (int folderId : deletedFolderIds) {
+                mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
+                mBgDataModel.folders.remove(folderId);
+                mBgDataModel.itemsIdMap.remove(folderId);
             }
+
+            // Remove any ghost widgets
+            LauncherSettings.Settings.call(contentResolver,
+                    LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS);
+        }
+
+        // Update pinned state of model shortcuts
+        mBgDataModel.updateShortcutPinnedState(context);
+
+        if (!Utilities.isBootCompleted() && !mPendingPackages.isEmpty()) {
+            context.registerReceiver(
+                    new SdCardAvailableReceiver(mApp, mPendingPackages),
+                    new IntentFilter(Intent.ACTION_BOOT_COMPLETED),
+                    null,
+                    MODEL_EXECUTOR.getHandler());
         }
     }
 
@@ -910,14 +884,6 @@
                         PackageInstallInfo.fromInstallingState(info));
             }
         }
-        for (AppInfo item : mBgDataModel.cachedPredictedItems) {
-            List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
-                    item.componentName.getPackageName(), item.user);
-            for (LauncherActivityInfo info : l) {
-                boolean quietMode = mUserManagerState.isUserQuiet(item.user);
-                mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
-            }
-        }
 
         mBgAllAppsList.setFlags(FLAG_QUIET_MODE_ENABLED,
                 mUserManagerState.isAnyProfileQuietModeEnabled());
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
new file mode 100644
index 0000000..3ed8809
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -0,0 +1,86 @@
+/*
+ * 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.model;
+
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import java.util.Map;
+
+/**
+ * Class to extend LauncherModel functionality to provide extra data
+ */
+public class ModelDelegate implements ResourceBasedOverride {
+
+    /**
+     * Creates and initializes a new instance of the delegate
+     */
+    public static ModelDelegate newInstance(
+            Context context, LauncherAppState app, AllAppsList appsList, BgDataModel dataModel) {
+        ModelDelegate delegate = Overrides.getObject(
+                ModelDelegate.class, context, R.string.model_delegate_class);
+
+        delegate.mApp = app;
+        delegate.mAppsList = appsList;
+        delegate.mDataModel = dataModel;
+        return delegate;
+    }
+
+    protected LauncherAppState mApp;
+    protected AllAppsList mAppsList;
+    protected BgDataModel mDataModel;
+
+    public ModelDelegate() { }
+
+    /**
+     * Called periodically to validate and update any data
+     */
+    @WorkerThread
+    public void validateData() {
+        if (hasShortcutsPermission(mApp.getContext())
+                != mAppsList.hasShortcutHostPermission()) {
+            mApp.getModel().forceReload();
+        }
+    }
+
+    /**
+     * Load delegate items if any in the data model
+     */
+    @WorkerThread
+    public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
+
+    /**
+     * Called during loader after workspace loading is complete
+     */
+    @WorkerThread
+    public void workspaceLoadComplete() { }
+
+    /**
+     * Called when the delegate is no loner needed
+     */
+    @WorkerThread
+    public void destroy() { }
+
+}
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 4efeba5..9b5fac8 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,17 +15,29 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.Utilities.isValidExtraType;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.util.Log;
+
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.IntStream;
 
 /**
@@ -33,6 +45,8 @@
  */
 public class ModelUtils {
 
+    private static final String TAG = "ModelUtils";
+
     /**
      * Filters the set of items who are directly or indirectly (via another container) on the
      * specified screen.
@@ -42,13 +56,7 @@
             ArrayList<T> currentScreenItems,
             ArrayList<T> otherScreenItems) {
         // Purge any null ItemInfos
-        Iterator<T> iter = allWorkspaceItems.iterator();
-        while (iter.hasNext()) {
-            ItemInfo i = iter.next();
-            if (i == null) {
-                iter.remove();
-            }
-        }
+        allWorkspaceItems.removeIf(Objects::isNull);
         // Order the set of items by their containers first, this allows use to walk through the
         // list sequentially, build up a list of containers that are in the specified screen,
         // as well as all items in those containers.
@@ -125,4 +133,52 @@
         IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
         return result;
     }
+
+
+    /**
+     * Creates a workspace item info for the legacy shortcut intent
+     */
+    @SuppressWarnings("deprecation")
+    public static WorkspaceItemInfo fromLegacyShortcutIntent(Context context, Intent data) {
+        if (!isValidExtraType(data, Intent.EXTRA_SHORTCUT_INTENT, Intent.class)
+                || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
+                        Intent.ShortcutIconResource.class))
+                || !(isValidExtraType(data, Intent.EXTRA_SHORTCUT_ICON, Bitmap.class))) {
+
+            Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+
+        Intent launchIntent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
+        String label = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
+        if (launchIntent == null || label == null) {
+            Log.e(TAG, "Invalid install shortcut intent");
+            return null;
+        }
+
+        final WorkspaceItemInfo info = new WorkspaceItemInfo();
+        info.user = Process.myUserHandle();
+
+        BitmapInfo iconInfo = null;
+        try (LauncherIcons li = LauncherIcons.obtain(context)) {
+            Bitmap bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
+            if (bitmap != null) {
+                iconInfo = li.createIconBitmap(bitmap);
+            } else {
+                info.iconResource = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
+                if (info.iconResource != null) {
+                    iconInfo = li.createIconBitmap(info.iconResource);
+                }
+            }
+        }
+
+        if (iconInfo == null) {
+            Log.e(TAG, "Invalid icon by the app");
+            return null;
+        }
+        info.bitmap = iconInfo;
+        info.contentDescription = info.title = Utilities.trim(label);
+        info.intent = launchIntent;
+        return info;
+    }
 }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index dca4ec0..896bfb6 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -24,16 +24,12 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
-import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.SessionCommitReceiver;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconCache;
@@ -108,11 +104,6 @@
                         appsList.removePackage(packages[i], mUser);
                     }
                     appsList.addPackage(context, packages[i], mUser);
-
-                    // Automatically add homescreen icon for work profile apps for below O device.
-                    if (!Utilities.ATLEAST_OREO && !Process.myUserHandle().equals(mUser)) {
-                        SessionCommitReceiver.queueAppIconAddition(context, packages[i], mUser);
-                    }
                 }
                 flagOp = FlagOp.removeFlag(WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE);
                 break;
@@ -328,10 +319,11 @@
             deleteAndBindComponentsRemoved(removeMatch);
 
             // Remove any queued items from the install queue
-            InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
+            ItemInstallQueue.INSTANCE.get(context)
+                    .removeFromInstallQueue(removedPackages, mUser);
         }
 
-        if (Utilities.ATLEAST_OREO && mOp == OP_ADD) {
+        if (mOp == OP_ADD) {
             // Load widgets for the new package. Changes due to app updates are handled through
             // AppWidgetHost events, this is just to initialize the long-press options.
             for (int i = 0; i < N; i++) {
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
deleted file mode 100644
index cb3903d..0000000
--- a/src/com/android/launcher3/model/PredictionModel.java
+++ /dev/null
@@ -1,136 +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.model;
-
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.UserHandle;
-
-import androidx.annotation.AnyThread;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.ResourceBasedOverride;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Model Helper for app predictions
- */
-public class PredictionModel implements ResourceBasedOverride {
-
-    private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
-    private static final int MAX_CACHE_ITEMS = 5;
-
-    protected Context mContext;
-    private SharedPreferences mDevicePrefs;
-    private UserCache mUserCache;
-
-
-    /**
-     * Retrieve instance of this object that can be overridden in runtime based on the build
-     * variant of the application.
-     */
-    public static PredictionModel newInstance(Context context) {
-        PredictionModel model = Overrides.getObject(PredictionModel.class, context,
-                R.string.prediction_model_class);
-        model.init(context);
-        return model;
-    }
-
-    protected void init(Context context) {
-        mContext = context;
-        mDevicePrefs = Utilities.getDevicePrefs(mContext);
-        mUserCache = UserCache.INSTANCE.get(mContext);
-
-    }
-
-    /**
-     * Formats and stores a list of component key in device preferences.
-     */
-    @AnyThread
-    public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
-        MODEL_EXECUTOR.execute(() -> {
-            LauncherAppState appState = LauncherAppState.getInstance(mContext);
-            StringBuilder builder = new StringBuilder();
-            int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
-            for (int i = 0; i < count; i++) {
-                builder.append(serializeComponentKeyToString(componentKeys.get(i)));
-                builder.append("\n");
-            }
-            if (componentKeys.isEmpty() /* should invalidate loader items */) {
-                appState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
-                    @Override
-                    public void execute(LauncherAppState app, BgDataModel model, AllAppsList apps) {
-                        model.cachedPredictedItems.clear();
-                    }
-                });
-            }
-            mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
-        });
-    }
-
-    /**
-     * parses and returns ComponentKeys saved by
-     * {@link PredictionModel#cachePredictionComponentKeys(List)}
-     */
-    @WorkerThread
-    public List<ComponentKey> getPredictionComponentKeys() {
-        Preconditions.assertWorkerThread();
-        ArrayList<ComponentKey> items = new ArrayList<>();
-        String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
-        for (String line : cachedBlob.split("\n")) {
-            ComponentKey key = getComponentKeyFromSerializedString(line);
-            if (key != null) {
-                items.add(key);
-            }
-
-        }
-        return items;
-    }
-
-    private String serializeComponentKeyToString(ComponentKey componentKey) {
-        long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
-        return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
-    }
-
-    private ComponentKey getComponentKeyFromSerializedString(String str) {
-        int sep = str.indexOf('#');
-        if (sep < 0 || (sep + 1) >= str.length()) {
-            return null;
-        }
-        ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
-        if (componentName == null) {
-            return null;
-        }
-        try {
-            long serialNumber = Long.parseLong(str.substring(sep + 1));
-            UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
-            return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
-        } catch (NumberFormatException ex) {
-            return null;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/SdCardAvailableReceiver.java b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
index eb3cb52..3798575 100644
--- a/src/com/android/launcher3/model/SdCardAvailableReceiver.java
+++ b/src/com/android/launcher3/model/SdCardAvailableReceiver.java
@@ -24,12 +24,11 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Map.Entry;
+import java.util.Set;
 
 /**
  * Helper class to re-query app status when SD-card becomes available.
@@ -42,10 +41,9 @@
 
     private final LauncherModel mModel;
     private final Context mContext;
-    private final MultiHashMap<UserHandle, String> mPackages;
+    private final Set<PackageUserKey> mPackages;
 
-    public SdCardAvailableReceiver(LauncherAppState app,
-            MultiHashMap<UserHandle, String> packages) {
+    public SdCardAvailableReceiver(LauncherAppState app, Set<PackageUserKey> packages) {
         mModel = app.getModel();
         mContext = app.getContext();
         mPackages = packages;
@@ -55,19 +53,17 @@
     public void onReceive(Context context, Intent intent) {
         final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
         final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
-        for (Entry<UserHandle, ArrayList<String>> entry : mPackages.entrySet()) {
-            UserHandle user = entry.getKey();
+        for (PackageUserKey puk : mPackages) {
+            UserHandle user = puk.mUser;
 
             final ArrayList<String> packagesRemoved = new ArrayList<>();
             final ArrayList<String> packagesUnavailable = new ArrayList<>();
 
-            for (String pkg : new HashSet<>(entry.getValue())) {
-                if (!launcherApps.isPackageEnabled(pkg, user)) {
-                    if (pmHelper.isAppOnSdcard(pkg, user)) {
-                        packagesUnavailable.add(pkg);
-                    } else {
-                        packagesRemoved.add(pkg);
-                    }
+            if (!launcherApps.isPackageEnabled(puk.mPackageName, user)) {
+                if (pmHelper.isAppOnSdcard(puk.mPackageName, user)) {
+                    packagesUnavailable.add(puk.mPackageName);
+                } else {
+                    packagesRemoved.add(puk.mPackageName);
                 }
             }
             if (!packagesRemoved.isEmpty()) {
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 88006ba..6fedad1 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -25,11 +25,12 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.MultiHashMap;
 
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Handles changes due to shortcut manager updates (deep shortcut changes)
@@ -53,54 +54,53 @@
     public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
         final Context context = app.getContext();
         // Find WorkspaceItemInfo's that have changed on the workspace.
-        HashSet<ShortcutKey> removedKeys = new HashSet<>();
-        MultiHashMap<ShortcutKey, WorkspaceItemInfo> keyToShortcutInfo = new MultiHashMap<>();
-        HashSet<String> allIds = new HashSet<>();
+        ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
 
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
                 if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                         && mPackageName.equals(si.getIntent().getPackage())) {
-                    keyToShortcutInfo.addToList(ShortcutKey.fromItemInfo(si), si);
-                    allIds.add(si.getDeepShortcutId());
+                    matchingWorkspaceItems.add(si);
                 }
             });
         }
 
-        final ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
-        if (!keyToShortcutInfo.isEmpty()) {
+        if (!matchingWorkspaceItems.isEmpty()) {
             // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
+                    .map(WorkspaceItemInfo::getDeepShortcutId)
+                    .distinct()
+                    .collect(Collectors.toList());
             List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
-                    .forPackage(mPackageName, new ArrayList<>(allIds))
+                    .forPackage(mPackageName, allLauncherKnownIds)
                     .query(ShortcutRequest.ALL);
+
+            Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
+            ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
             for (ShortcutInfo fullDetails : shortcuts) {
-                ShortcutKey key = ShortcutKey.fromInfo(fullDetails);
-                List<WorkspaceItemInfo> workspaceItemInfos = keyToShortcutInfo.remove(key);
                 if (!fullDetails.isPinned()) {
-                    // The shortcut was previously pinned but is no longer, so remove it from
-                    // the workspace and our pinned shortcut counts.
-                    // Note that we put this check here, after querying for full details,
-                    // because there's a possible race condition between pinning and
-                    // receiving this callback.
-                    removedKeys.add(key);
                     continue;
                 }
-                for (final WorkspaceItemInfo workspaceItemInfo : workspaceItemInfos) {
-                    workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                    app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
-                    updatedWorkspaceItemInfos.add(workspaceItemInfo);
-                }
+
+                String sid = fullDetails.getId();
+                nonPinnedIds.remove(sid);
+                matchingWorkspaceItems
+                        .stream()
+                        .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
+                        .forEach(workspaceItemInfo -> {
+                            workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
+                            app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
+                            updatedWorkspaceItemInfos.add(workspaceItemInfo);
+                        });
             }
-        }
 
-        // If there are still entries in keyToShortcutInfo, that means that
-        // the corresponding shortcuts weren't passed in onShortcutsChanged(). This
-        // means they were cleared, so we remove and unpin them now.
-        removedKeys.addAll(keyToShortcutInfo.keySet());
-
-        bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
-        if (!keyToShortcutInfo.isEmpty()) {
-            deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(removedKeys));
+            bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
+            if (!nonPinnedIds.isEmpty()) {
+                deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
+                        nonPinnedIds.stream()
+                                .map(id -> new ShortcutKey(mPackageName, mUser, id))
+                                .collect(Collectors.toSet())));
+            }
         }
 
         if (mUpdateIdMap) {
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index b17b062..aee1f2a 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -138,8 +138,7 @@
         info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
                 ? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
 
-        if (Utilities.ATLEAST_OREO
-                && appInfo.targetSdkVersion >= Build.VERSION_CODES.O
+        if (appInfo.targetSdkVersion >= Build.VERSION_CODES.O
                 && Process.myUserHandle().equals(lai.getUser())) {
             // The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
             info.runtimeStatusFlags |= FLAG_ADAPTIVE_ICON;
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 59233cd..e03fd72 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -258,12 +258,6 @@
     }
 
     /**
-     * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
-     */
-    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
-    }
-
-    /**
      * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
      */
     public LauncherAtom.ItemInfo buildProto() {
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index b0d19a6..c04b7f0 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.AppWidgetResizeFrame;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.util.ContentWriter;
 
 /**
@@ -195,13 +194,4 @@
     public final boolean hasOptionFlag(int option) {
         return (options & option) != 0;
     }
-
-    @Override
-    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
-        builder.setWidget(LauncherAtom.Widget.newBuilder()
-                .setSpanX(spanX)
-                .setSpanY(spanY)
-                .setComponentName(providerName.toString())
-                .setPackageName(providerName.getPackageName()));
-    }
 }
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index a7bf1f3..1e1d093 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -216,7 +216,7 @@
         if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT || hasStatusFlag(
                 FLAG_SUPPORTS_WEB_UI | FLAG_AUTOINSTALL_ICON | FLAG_RESTORED_ICON))) {
             // Legacy shortcuts and promise icons with web UI may not have a componentName but just
-            // a packageName. In that case create a dummy componentName instead of adding additional
+            // a packageName. In that case create a empty componentName instead of adding additional
             // check everywhere.
             String pkg = intent.getPackage();
             return pkg == null ? null : new ComponentName(pkg, IconCache.EMPTY_CLASS_NAME);
diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java
index 835f72d..80eeb22 100644
--- a/src/com/android/launcher3/notification/NotificationInfo.java
+++ b/src/com/android/launcher3/notification/NotificationInfo.java
@@ -106,7 +106,6 @@
                 view, 0, 0, view.getWidth(), view.getHeight()).toBundle();
         try {
             intent.send(null, 0, null, null, null, null, activityOptions);
-            launcher.getUserEventDispatcher().logNotificationLaunch(view, intent);
             launcher.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_NOTIFICATION_LAUNCH_TAP);
         } catch (PendingIntent.CanceledException e) {
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index a1917ec..1dda3df 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -30,9 +30,9 @@
 
 /**
  * The key data associated with the notification, used to determine what to include
- * in dots and dummy popup views before they are populated.
+ * in dots and stub popup views before they are populated.
  *
- * @see NotificationInfo for the full data used when populating the dummy views.
+ * @see NotificationInfo for the full data used when populating the stub views.
  */
 public class NotificationKeyData {
     public final String notificationKey;
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index b03aa9c..32f060b 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -137,7 +137,7 @@
             setOnClickListener(mNotificationInfo);
         }
         setContentTranslation(0);
-        // Add a dummy ItemInfo so that logging populates the correct container and item types
+        // Add a stub ItemInfo so that logging populates the correct container and item types
         // instead of DEFAULT_CONTAINERTYPE and DEFAULT_ITEMTYPE, respectively.
         setTag(NOTIFICATION_ITEM_INFO);
         if (animate) {
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 901d27f..753a6dd 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.SessionCommitReceiver;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperExecutor;
@@ -167,7 +168,7 @@
      * Attempt to restore workspace layout if the session is triggered due to device restore.
      */
     public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
-        if (!Utilities.ATLEAST_OREO || !FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
+        if (!FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
             return false;
         }
         if (isRestore(info)) {
@@ -203,7 +204,7 @@
      * - A promise icon for the session has not already been created
      */
     void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
-        if (Utilities.ATLEAST_OREO && FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
+        if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                 && SessionCommitReceiver.isEnabled(mAppContext)
                 && verify(sessionInfo) != null
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
@@ -212,7 +213,9 @@
                 && !mPromiseIconIds.contains(sessionInfo.getSessionId())
                 && new PackageManagerHelper(mAppContext).getApplicationInfo(
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
-            SessionCommitReceiver.queuePromiseAppIconAddition(mAppContext, sessionInfo);
+            ItemInstallQueue.INSTANCE.get(mAppContext)
+                    .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
+
             mPromiseIconIds.add(sessionInfo.getSessionId());
             updatePromiseIconPrefs();
         }
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index 40d7031..7af14c6 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -27,7 +27,6 @@
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.Process;
@@ -39,13 +38,13 @@
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.PackageUserKey;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -175,37 +174,23 @@
         List<ShortcutConfigActivityInfo> result = new ArrayList<>();
         UserHandle myUser = Process.myUserHandle();
 
-        if (Utilities.ATLEAST_OREO) {
-            final List<UserHandle> users;
-            final String packageName;
-            if (packageUser == null) {
-                users = UserCache.INSTANCE.get(context).getUserProfiles();
-                packageName = null;
-            } else {
-                users = new ArrayList<>(1);
-                users.add(packageUser.mUser);
-                packageName = packageUser.mPackageName;
-            }
-            LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
-            for (UserHandle user : users) {
-                boolean ignoreTargetSdk = myUser.equals(user);
-                for (LauncherActivityInfo activityInfo :
-                        launcherApps.getShortcutConfigActivityList(packageName, user)) {
-                    if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
-                            >= Build.VERSION_CODES.O) {
-                        result.add(new ShortcutConfigActivityInfoVO(activityInfo));
-                    }
-                }
-            }
+        final List<UserHandle> users;
+        final String packageName;
+        if (packageUser == null) {
+            users = UserCache.INSTANCE.get(context).getUserProfiles();
+            packageName = null;
         } else {
-            if (packageUser == null || packageUser.mUser.equals(myUser)) {
-                Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
-                if (packageUser != null) {
-                    intent.setPackage(packageUser.mPackageName);
-                }
-                for (ResolveInfo info :
-                        context.getPackageManager().queryIntentActivities(intent, 0)) {
-                    result.add(new ShortcutConfigActivityInfoVL(info.activityInfo));
+            users = Collections.singletonList(packageUser.mUser);
+            packageName = packageUser.mPackageName;
+        }
+        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+        for (UserHandle user : users) {
+            boolean ignoreTargetSdk = myUser.equals(user);
+            for (LauncherActivityInfo activityInfo :
+                    launcherApps.getShortcutConfigActivityList(packageName, user)) {
+                if (ignoreTargetSdk || activityInfo.getApplicationInfo().targetSdkVersion
+                        >= Build.VERSION_CODES.O) {
+                    result.add(new ShortcutConfigActivityInfoVO(activityInfo));
                 }
             }
         }
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 5aab41a..2d7d6b0 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -21,8 +21,10 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.LongSparseArray;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -102,6 +104,9 @@
                 mUsers = null;
                 mUserToSerialMap = null;
             }
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work profile removed", new Exception());
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 614cf14..26b32b8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -20,12 +20,9 @@
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
-import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.AnimatorSet;
@@ -52,7 +49,6 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
@@ -148,17 +144,6 @@
         return (type & TYPE_ACTION_POPUP) != 0;
     }
 
-    @Override
-    public void logActionCommand(int command) {
-        mLauncher.getUserEventDispatcher().logActionCommand(
-                command, mOriginalIcon, getLogContainerType());
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.DEEPSHORTCUTS;
-    }
-
     public OnClickListener getItemClickListener() {
         return (view) -> {
             mLauncher.getItemOnClickListener().onClick(view);
@@ -400,9 +385,7 @@
         } else if (view instanceof ImageView) {
             // Only the system shortcut icon shows on a gray background header.
             info.setIconAndContentDescriptionFor((ImageView) view);
-            if (Utilities.ATLEAST_OREO) {
-                view.setTooltipText(view.getContentDescription());
-            }
+            view.setTooltipText(view.getContentDescription());
         }
         view.setTag(info);
         view.setOnClickListener(info);
@@ -499,18 +482,6 @@
     }
 
     @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        if (childInfo == NOTIFICATION_ITEM_INFO) {
-            child.itemType = ItemType.NOTIFICATION;
-        } else {
-            child.itemType = ItemType.DEEPSHORTCUT;
-            child.rank = childInfo.rank;
-        }
-        parents.add(newContainerTarget(ContainerType.DEEPSHORTCUTS));
-    }
-
-    @Override
     protected void onCreateCloseAnimation(AnimatorSet anim) {
         // Animate original icon's text back in.
         anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 5a5f668..76048ba 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -38,7 +38,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -202,20 +201,11 @@
     }
 
     public List<WidgetItem> getWidgetsForPackageUser(PackageUserKey packageUserKey) {
-        for (WidgetListRowEntry entry : mAllWidgets) {
-            if (entry.pkgItem.packageName.equals(packageUserKey.mPackageName)) {
-                ArrayList<WidgetItem> widgets = new ArrayList<>(entry.widgets);
-                // Remove widgets not associated with the correct user.
-                Iterator<WidgetItem> iterator = widgets.iterator();
-                while (iterator.hasNext()) {
-                    if (!iterator.next().user.equals(packageUserKey.mUser)) {
-                        iterator.remove();
-                    }
-                }
-                return widgets.isEmpty() ? null : widgets;
-            }
-        }
-        return null;
+        return mAllWidgets.stream()
+                .filter(row -> row.pkgItem.packageName.equals(packageUserKey.mPackageName))
+                .flatMap(row -> row.widgets.stream())
+                .filter(widget -> packageUserKey.mUser.equals(widget.user))
+                .collect(Collectors.toList());
     }
 
     /**
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 7998488..81302ac 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -99,7 +99,7 @@
         final List<WidgetItem> widgets =
                 launcher.getPopupDataProvider().getWidgetsForPackageUser(new PackageUserKey(
                         itemInfo.getTargetComponent().getPackageName(), itemInfo.user));
-        if (widgets == null) {
+        if (widgets.isEmpty()) {
             return null;
         }
         return new Widgets(launcher, itemInfo);
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index a013312..2b04365 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -20,6 +20,7 @@
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.Display;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewAnimationUtils;
@@ -168,7 +169,9 @@
 
     @Override
     public ActivityOptions getActivityLaunchOptions(View v) {
-        return null;
+        final Display display = getWindow().getDecorView().getDisplay();
+        return display != null ? ActivityOptions.makeBasic().setLaunchDisplayId(
+                       display.getDisplayId()) : null;
     }
 
     @Override
@@ -194,9 +197,6 @@
     public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
 
     @Override
-    public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
-
-    @Override
     public void bindScreens(IntArray orderedScreenIds) { }
 
     @Override
@@ -222,7 +222,7 @@
     }
 
     @Override
-    public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
 
     @Override
     public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index b12d04f..4baecb7 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -34,17 +34,23 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.text.Editable;
+import android.text.TextWatcher;
 import android.util.ArrayMap;
 import android.util.Pair;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.View;
+import android.widget.EditText;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceDataStore;
 import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
@@ -81,6 +87,7 @@
     private PreferenceCategory mPluginsCategory;
     private FlagTogglerPrefUi mFlagTogglerPrefUi;
 
+
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
@@ -99,6 +106,50 @@
         maybeAddSandboxCategory();
     }
 
+    private void filterPreferences(String query, PreferenceGroup pg) {
+        int count = pg.getPreferenceCount();
+        int hidden = 0;
+        for (int i = 0; i < count; i++) {
+            Preference preference = pg.getPreference(i);
+            if (preference instanceof PreferenceGroup) {
+                filterPreferences(query, (PreferenceGroup) preference);
+            } else {
+                String title = preference.getTitle().toString().toLowerCase().replace("_", " ");
+                if (query.isEmpty() || title.contains(query)) {
+                    preference.setVisible(true);
+                } else {
+                    preference.setVisible(false);
+                    hidden++;
+                }
+            }
+        }
+        pg.setVisible(hidden != count);
+    }
+
+    @Override
+    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+        EditText filterBox = view.findViewById(R.id.filter_box);
+        filterBox.setVisibility(View.VISIBLE);
+        filterBox.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
+
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+                String query = editable.toString().toLowerCase().replace("_", " ");
+                filterPreferences(query, mPreferenceScreen);
+            }
+        });
+    }
+
     @Override
     public void onDestroy() {
         super.onDestroy();
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index d3213a1..922425f 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -18,7 +18,6 @@
 
 import static androidx.core.view.accessibility.AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS;
 
-import static com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.states.RotationHelper.getAllowRotationDefaultValue;
 import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
@@ -45,6 +44,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SecureSettingsObserver;
 
@@ -172,8 +172,7 @@
         protected boolean initPreference(Preference preference) {
             switch (preference.getKey()) {
                 case NOTIFICATION_DOTS_PREFERENCE_KEY:
-                    if (!Utilities.ATLEAST_OREO ||
-                            !getResources().getBoolean(R.bool.notification_dots_enabled)) {
+                    if (WidgetsModel.GO_DISABLE_NOTIFICATION_DOTS) {
                         return false;
                     }
 
@@ -188,9 +187,6 @@
                     mNotificationDotsObserver.dispatchOnChange();
                     return true;
 
-                case ADD_ICON_PREFERENCE_KEY:
-                    return Utilities.ATLEAST_OREO;
-
                 case ALLOW_ROTATION_PREFERENCE_KEY:
                     if (getResources().getBoolean(R.bool.allow_rotation)) {
                         // Launcher supports rotation by default. No need to show this setting.
diff --git a/src/com/android/launcher3/shortcuts/ShortcutKey.java b/src/com/android/launcher3/shortcuts/ShortcutKey.java
index 3ca9490..0c6d675 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutKey.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutKey.java
@@ -30,6 +30,10 @@
         return componentName.getClassName();
     }
 
+    public String getPackageName() {
+        return componentName.getPackageName();
+    }
+
     /**
      * Creates a {@link ShortcutRequest} for this key
      */
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index f1e7f16..beb5b68 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.statemanager;
 
+import static android.animation.ValueAnimator.areAnimatorsEnabled;
+
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 
 import android.animation.Animator;
@@ -26,7 +28,6 @@
 import android.os.Looper;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
@@ -179,7 +180,7 @@
 
     private void goToState(STATE_TYPE state, boolean animated, long delay,
             final Runnable onCompleteRunnable) {
-        animated &= Utilities.areAnimationsEnabled(mActivity);
+        animated &= areAnimatorsEnabled();
         if (mActivity.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
@@ -311,11 +312,16 @@
                 handler.setStateWithAnimation(state, mConfig, builder);
             }
         }
-        builder.addListener(new AnimationSuccessListener() {
+        builder.addListener(createStateAnimationListener(state));
+        mConfig.setAnimation(builder.buildAnim(), state);
+        return builder;
+    }
+
+    private AnimatorListener createStateAnimationListener(STATE_TYPE state) {
+        return new AnimationSuccessListener() {
 
             @Override
             public void onAnimationStart(Animator animation) {
-                super.onAnimationStart(animation);
                 // Change the internal state only when the transition actually starts
                 onStateTransitionStart(state);
             }
@@ -327,9 +333,7 @@
                 }
                 onStateTransitionEnd(state);
             }
-        });
-        mConfig.setAnimation(builder.buildAnim(), state);
-        return builder;
+        };
     }
 
     private void onStateTransitionStart(STATE_TYPE state) {
@@ -397,6 +401,19 @@
     }
 
     /**
+     * @see #setCurrentAnimation(AnimatorSet, Animator...). Using this method tells the StateManager
+     * that this is a custom animation to the given state, and thus the StateManager will add an
+     * animation listener to call {@link #onStateTransitionStart} and {@link #onStateTransitionEnd}.
+     * @param anim The custom animation to the given state.
+     * @param toState The state we are animating towards.
+     */
+    public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+        cancelAnimation();
+        setCurrentAnimation(anim);
+        anim.addListener(createStateAnimationListener(toState));
+    }
+
+    /**
      * Sets the animation as the current state animation, i.e., canceled when
      * starting another animation and may block some launcher interactions while running.
      *
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
index f90ad3c..8b72177 100644
--- a/src/com/android/launcher3/states/StateAnimationConfig.java
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -71,6 +71,7 @@
             ANIM_ALL_APPS_HEADER_FADE,
             ANIM_OVERVIEW_MODAL,
             ANIM_DEPTH,
+            ANIM_OVERVIEW_ACTIONS_FADE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AnimType {}
@@ -89,10 +90,11 @@
     public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
     public static final int ANIM_OVERVIEW_MODAL = 13;
     public static final int ANIM_DEPTH = 14;
+    public static final int ANIM_OVERVIEW_ACTIONS_FADE = 15;
 
-    private static final int ANIM_TYPES_COUNT = 15;
+    private static final int ANIM_TYPES_COUNT = 16;
 
-    private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
+    protected final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
 
     public StateAnimationConfig() { }
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 8165627..b2d0081 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -27,12 +27,11 @@
     public static final int NORMAL_STATE_ORDINAL = 0;
     public static final int SPRING_LOADED_STATE_ORDINAL = 1;
     public static final int OVERVIEW_STATE_ORDINAL = 2;
-    public static final int OVERVIEW_PEEK_STATE_ORDINAL = 3;
-    public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 4;
-    public static final int QUICK_SWITCH_STATE_ORDINAL = 5;
-    public static final int ALL_APPS_STATE_ORDINAL = 6;
-    public static final int BACKGROUND_APP_STATE_ORDINAL = 7;
-    public static final int HINT_STATE_ORDINAL = 8;
+    public static final int OVERVIEW_MODAL_TASK_STATE_ORDINAL = 3;
+    public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
+    public static final int ALL_APPS_STATE_ORDINAL = 5;
+    public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+    public static final int HINT_STATE_ORDINAL = 7;
     public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
@@ -46,8 +45,6 @@
                 return "SpringLoaded";
             case OVERVIEW_STATE_ORDINAL:
                 return "Overview";
-            case OVERVIEW_PEEK_STATE_ORDINAL:
-                return "OverviewPeek";
             case OVERVIEW_MODAL_TASK_STATE_ORDINAL:
                 return "OverviewModal";
             case QUICK_SWITCH_STATE_ORDINAL:
@@ -98,7 +95,9 @@
     public static final String REQUEST_ENABLE_DEBUG_TRACING = "enable-debug-tracing";
     public static final String REQUEST_DISABLE_DEBUG_TRACING = "disable-debug-tracing";
 
-    public static final String REQUEST_OVERVIEW_ACTIONS_ENABLED = "overview-actions-enabled";
+    public static final String REQUEST_OVERVIEW_SHARE_ENABLED = "overview-share-enabled";
+    public static final String REQUEST_OVERVIEW_CONTENT_PUSH_ENABLED =
+            "overview-content-push-enabled";
 
     public static boolean sDisableSensorRotation;
     public static final String REQUEST_MOCK_SENSOR_ROTATION = "mock-sensor-rotation";
@@ -107,4 +106,5 @@
     public static final String PAUSE_NOT_DETECTED = "b/139891609";
     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";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 3c78b08..8ee5a6e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -26,7 +26,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
 import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -37,6 +37,8 @@
 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;
@@ -44,6 +46,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -265,8 +268,10 @@
         mCanBlockFling = mFromState == NORMAL;
         mFlingBlockCheck.unblockFling();
         // Must be called after all the animation controllers have been paused
-        if (mToState == ALL_APPS || mToState == NORMAL) {
-            mLauncher.getAllAppsController().onDragStart(mToState == ALL_APPS);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()
+                && (mToState == ALL_APPS || mToState == NORMAL)) {
+            mLauncher.getAllAppsController().getInsetController().onDragStart(
+                    mToState == ALL_APPS ? 0 : 1);
         }
     }
 
@@ -508,15 +513,7 @@
         if (mAtomicAnim == null) {
             return 0;
         }
-        if (Utilities.ATLEAST_OREO) {
-            return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
-        } else {
-            long remainingDuration = 0;
-            for (Animator anim : mAtomicAnim.getChildAnimations()) {
-                remainingDuration = Math.max(remainingDuration, anim.getDuration());
-            }
-            return remainingDuration;
-        }
+        return mAtomicAnim.getTotalDuration() - mAtomicAnim.getCurrentPlayTime();
     }
 
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index ac1ade2..fb02f79 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -72,6 +72,7 @@
         out.halfPageSize = view.getNormalChildHeight() / 2;
         out.halfScreenSize = view.getMeasuredHeight() / 2;
         out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleY();
     }
 
     @Override
@@ -107,6 +108,11 @@
     }
 
     @Override
+    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
+        action.call(target, param, 0);
+    }
+
+    @Override
     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
         return event.getY(pointerIndex);
     }
@@ -147,8 +153,9 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
-        view.setTranslationX(0);
+    public void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY) {
+        view.setTranslationX(defaultTranslationX);
         view.setTranslationY(translation);
     }
 
@@ -214,7 +221,11 @@
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getPrimaryTranslationDirectionFactor() {
+        return -1;
+    }
+
+    public int getSecondaryTranslationDirectionFactor() {
         return 1;
     }
 
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index d4f5cba..354d78d 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -57,6 +57,7 @@
 
     <T> void set(T target, Int2DAction<T> action, int param);
     <T> void set(T target, Float2DAction<T> action, float param);
+    <T> void setSecondary(T target, Float2DAction<T> action, float param);
     float getPrimaryDirection(MotionEvent event, int pointerIndex);
     float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId);
     int getMeasuredSize(View view);
@@ -65,7 +66,8 @@
     int getSecondaryDimension(View view);
     FloatProperty<View> getPrimaryViewTranslate();
     FloatProperty<View> getSecondaryViewTranslate();
-    void setPrimaryAndResetSecondaryTranslate(View view, float translation);
+    void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY);
     int getPrimaryScroll(View view);
     float getPrimaryScale(View view);
     int getChildStart(View view);
@@ -74,7 +76,8 @@
     int getScrollOffsetStart(View view, Rect insets);
     int getScrollOffsetEnd(View view, Rect insets);
     SingleAxisSwipeDetector.Direction getOppositeSwipeDirection();
-    int getTaskDismissDirectionFactor();
+    int getPrimaryTranslationDirectionFactor();
+    int getSecondaryTranslationDirectionFactor();
     int getTaskDragDisplacementFactor(boolean isRtl);
     ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild);
     void setMaxScroll(AccessibilityEvent event, int maxScroll);
@@ -109,6 +112,7 @@
         public int halfPageSize;
         public int screenCenter;
         public int halfScreenSize;
+        public float pageParentScale;
     }
 
     class ChildBounds {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 3341996..06479e6 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -70,6 +70,7 @@
         out.halfPageSize = view.getNormalChildWidth() / 2;
         out.halfScreenSize = view.getMeasuredWidth() / 2;
         out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleX();
     }
 
     @Override
@@ -104,6 +105,11 @@
     }
 
     @Override
+    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
+        action.call(target, 0, param);
+    }
+
+    @Override
     public float getPrimaryDirection(MotionEvent event, int pointerIndex) {
         return event.getX(pointerIndex);
     }
@@ -144,9 +150,10 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
+    public void setPrimaryAndResetSecondaryTranslate(
+            View view, float translation, float defaultTranslationX, float defaultTranslationY) {
         view.setTranslationX(translation);
-        view.setTranslationY(0);
+        view.setTranslationY(defaultTranslationY);
     }
 
     @Override
@@ -211,7 +218,11 @@
     }
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getPrimaryTranslationDirectionFactor() {
+        return 1;
+    }
+
+    public int getSecondaryTranslationDirectionFactor() {
         return -1;
     }
 
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index e91f16d..60d19d9 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -28,7 +28,7 @@
 public class SeascapePagedViewHandler extends LandscapePagedViewHandler {
 
     @Override
-    public int getTaskDismissDirectionFactor() {
+    public int getSecondaryTranslationDirectionFactor() {
         return -1;
     }
 
@@ -75,12 +75,6 @@
     }
 
     @Override
-    public void setPrimaryAndResetSecondaryTranslate(View view, float translation) {
-        view.setTranslationX(0);
-        view.setTranslationY(translation);
-    }
-
-    @Override
     public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
         return dp.widthPx - rect.right;
     }
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index 0f81520..b3b69f6 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -26,14 +26,16 @@
 import android.graphics.Point;
 import android.util.Log;
 
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
+
 import java.util.function.Consumer;
 
 /**
  * {@link BroadcastReceiver} which watches configuration changes and
  * notifies the callback in case changes which affect the device profile occur.
  */
-public class ConfigMonitor extends BroadcastReceiver implements
-        DefaultDisplay.DisplayInfoChangeListener {
+public class ConfigMonitor extends BroadcastReceiver implements DisplayInfoChangeListener {
 
     private static final String TAG = "ConfigMonitor";
 
@@ -57,9 +59,9 @@
         mFontScale = config.fontScale;
         mDensity = config.densityDpi;
 
-        DefaultDisplay display = DefaultDisplay.INSTANCE.get(context);
+        DisplayController.DisplayHolder display = DisplayController.getDefaultDisplay(context);
         display.addChangeListener(this);
-        DefaultDisplay.Info displayInfo = display.getInfo();
+        Info displayInfo = display.getInfo();
         mDisplayId = displayInfo.id;
 
         mRealSize = new Point(displayInfo.realSize);
@@ -82,7 +84,7 @@
     }
 
     @Override
-    public void onDisplayInfoChanged(DefaultDisplay.Info info, int flags) {
+    public void onDisplayInfoChanged(Info info, int flags) {
         if (info.id != mDisplayId) {
             return;
         }
@@ -113,8 +115,7 @@
     public void unregister() {
         try {
             mContext.unregisterReceiver(this);
-            DefaultDisplay display = DefaultDisplay.INSTANCE.get(mContext);
-            display.removeChangeListener(this);
+            DisplayController.getDefaultDisplay(mContext).removeChangeListener(this);
         } catch (Exception e) {
             Log.e(TAG, "Failed to unregister config monitor", e);
         }
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
deleted file mode 100644
index 35788a5..0000000
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManager.DisplayListener;
-import android.os.Handler;
-import android.os.Message;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-
-import androidx.annotation.VisibleForTesting;
-
-import java.util.ArrayList;
-
-/**
- * Utility class to cache properties of default display to avoid a system RPC on every call.
- */
-public class DefaultDisplay implements DisplayListener {
-
-    public static final MainThreadInitializedObject<DefaultDisplay> INSTANCE =
-            new MainThreadInitializedObject<>(DefaultDisplay::new);
-
-    private static final String TAG = "DefaultDisplay";
-
-    public static final int CHANGE_SIZE = 1 << 0;
-    public static final int CHANGE_ROTATION = 1 << 1;
-    public static final int CHANGE_FRAME_DELAY = 1 << 2;
-
-    public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
-
-    private final Context mDisplayContext;
-    private final int mId;
-    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
-    private final Handler mChangeHandler;
-    private Info mInfo;
-
-    private DefaultDisplay(Context context) {
-        DisplayManager dm = context.getSystemService(DisplayManager.class);
-        // Use application context to create display context so that it can have its own Resources.
-        mDisplayContext = context.getApplicationContext().createDisplayContext(
-                dm.getDisplay(DEFAULT_DISPLAY));
-        // Note that the Display object must be obtained from DisplayManager which is associated to
-        // the display context, so the Display is isolated from Activity and Application to provide
-        // the actual state of device that excludes the additional adjustment and override.
-        mInfo = new Info(mDisplayContext);
-        mId = mInfo.id;
-        mChangeHandler = new Handler(this::onChange);
-
-        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
-    }
-
-    @Override
-    public final void onDisplayAdded(int displayId) {  }
-
-    @Override
-    public final void onDisplayRemoved(int displayId) { }
-
-    @Override
-    public final void onDisplayChanged(int displayId) {
-        if (displayId != mId) {
-            return;
-        }
-
-        Info oldInfo = mInfo;
-        Info info = new Info(mDisplayContext);
-
-        int change = 0;
-        if (info.hasDifferentSize(oldInfo)) {
-            change |= CHANGE_SIZE;
-        }
-        if (oldInfo.rotation != info.rotation) {
-            change |= CHANGE_ROTATION;
-        }
-        if (info.singleFrameMs != oldInfo.singleFrameMs) {
-            change |= CHANGE_FRAME_DELAY;
-        }
-
-        if (change != 0) {
-            mInfo = info;
-            mChangeHandler.sendEmptyMessage(change);
-        }
-    }
-
-    public static int getSingleFrameMs(Context context) {
-        return INSTANCE.get(context).getInfo().singleFrameMs;
-    }
-
-    public Info getInfo() {
-        return mInfo;
-    }
-
-    public void addChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.add(listener);
-    }
-
-    public void removeChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.remove(listener);
-    }
-
-    private boolean onChange(Message msg) {
-        for (int i = mListeners.size() - 1; i >= 0; i--) {
-            mListeners.get(i).onDisplayInfoChanged(mInfo, msg.what);
-        }
-        return true;
-    }
-
-    public static class Info {
-
-        public final int id;
-        public final int rotation;
-        public final int singleFrameMs;
-
-        public final Point realSize;
-        public final Point smallestSize;
-        public final Point largestSize;
-
-        public final DisplayMetrics metrics;
-
-        @VisibleForTesting
-        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
-                Point largestSize, DisplayMetrics metrics) {
-            this.id = id;
-            this.rotation = rotation;
-            this.singleFrameMs = singleFrameMs;
-            this.realSize = realSize;
-            this.smallestSize = smallestSize;
-            this.largestSize = largestSize;
-            this.metrics = metrics;
-        }
-
-        private Info(Context context) {
-            this(context, context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY));
-        }
-
-        public Info(Context context, Display display) {
-            id = display.getDisplayId();
-            rotation = display.getRotation();
-
-            float refreshRate = display.getRefreshRate();
-            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
-
-            realSize = new Point();
-            smallestSize = new Point();
-            largestSize = new Point();
-            display.getRealSize(realSize);
-            display.getCurrentSizeRange(smallestSize, largestSize);
-
-            metrics = context.getResources().getDisplayMetrics();
-        }
-
-        private boolean hasDifferentSize(Info info) {
-            if (!realSize.equals(info.realSize)
-                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
-                Log.d(TAG, String.format("Display size changed from %s to %s",
-                        info.realSize, realSize));
-                return true;
-            }
-
-            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
-                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
-                        smallestSize, largestSize, info.smallestSize, info.largestSize));
-                return true;
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * Interface for listening for display changes
-     */
-    public interface DisplayInfoChangeListener {
-
-        void onDisplayInfoChanged(Info info, int flags);
-    }
-}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
new file mode 100644
index 0000000..355c949
--- /dev/null
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManager.DisplayListener;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * Utility class to cache properties of default display to avoid a system RPC on every call.
+ */
+public class DisplayController implements DisplayListener {
+
+    private static final String TAG = "DisplayController";
+
+    public static final MainThreadInitializedObject<DisplayController> INSTANCE =
+            new MainThreadInitializedObject<>(DisplayController::new);
+
+    private final SparseArray<DisplayHolder> mOtherDisplays = new SparseArray<>(0);
+    // We store the default display separately, to avoid null checks for primary use case.
+    private final DisplayHolder mDefaultDisplay;
+
+    private final ArrayList<DisplayListChangeListener> mListListeners = new ArrayList<>();
+
+    private DisplayController(Context context) {
+        mDefaultDisplay = DisplayHolder.create(context, DEFAULT_DISPLAY);
+
+        DisplayManager dm = context.getSystemService(DisplayManager.class);
+        dm.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
+    }
+
+    @Override
+    public final void onDisplayAdded(int displayId) {
+        DisplayHolder holder = DisplayHolder.create(mDefaultDisplay.mDisplayContext, displayId);
+        if (holder == null) {
+            // Display is already removed by the time we dot this.
+            return;
+        }
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.put(displayId, holder);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayAdded(holder)));
+    }
+
+    @Override
+    public final void onDisplayRemoved(int displayId) {
+        synchronized (mOtherDisplays) {
+            mOtherDisplays.remove(displayId);
+        }
+        MAIN_EXECUTOR.execute(() -> mListListeners.forEach(l-> l.onDisplayRemoved(displayId)));
+    }
+
+    /**
+     * Returns the holder corresponding to the given display
+     */
+    public DisplayHolder getHolder(int displayId) {
+        if (displayId == mDefaultDisplay.mId) {
+            return mDefaultDisplay;
+        } else {
+            synchronized (mOtherDisplays) {
+                return mOtherDisplays.get(displayId);
+            }
+        }
+    }
+
+    /**
+     * Adds a listener for display list changes
+     */
+    public void addListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.add(listener);
+    }
+
+    /**
+     * Removes a previously added display list change listener
+     */
+    public void removeListChangeListener(DisplayListChangeListener listener) {
+        mListListeners.remove(listener);
+    }
+
+    @Override
+    public final void onDisplayChanged(int displayId) {
+        DisplayHolder holder = getHolder(displayId);
+        if (holder != null) {
+            holder.handleOnChange();
+        }
+    }
+
+    public static int getSingleFrameMs(Context context) {
+        return getDefaultDisplay(context).getInfo().singleFrameMs;
+    }
+
+    public static DisplayHolder getDefaultDisplay(Context context) {
+        return INSTANCE.get(context).mDefaultDisplay;
+    }
+
+    /**
+     * A listener to receiving addition or removal of new displays
+     */
+    public interface DisplayListChangeListener {
+
+        /**
+         * Called when a new display is added
+         */
+        void onDisplayAdded(DisplayHolder holder);
+
+        /**
+         * Called when a previously added display is removed
+         */
+        void onDisplayRemoved(int displayId);
+    }
+
+    /**
+     * Interface for listening for display changes
+     */
+    public interface DisplayInfoChangeListener {
+
+        void onDisplayInfoChanged(Info info, int flags);
+    }
+
+    public static class DisplayHolder {
+
+        public static final int CHANGE_SIZE = 1 << 0;
+        public static final int CHANGE_ROTATION = 1 << 1;
+        public static final int CHANGE_FRAME_DELAY = 1 << 2;
+
+        public static final int CHANGE_ALL = CHANGE_SIZE | CHANGE_ROTATION | CHANGE_FRAME_DELAY;
+
+        final Context mDisplayContext;
+        final int mId;
+        private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+        private DisplayController.Info mInfo;
+
+        private DisplayHolder(Context displayContext) {
+            mDisplayContext = displayContext;
+            // Note that the Display object must be obtained from DisplayManager which is
+            // associated to the display context, so the Display is isolated from Activity and
+            // Application to provide the actual state of device that excludes the additional
+            // adjustment and override.
+            mInfo = new DisplayController.Info(mDisplayContext);
+            mId = mInfo.id;
+        }
+
+        public void addChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.add(listener);
+        }
+
+        public void removeChangeListener(DisplayInfoChangeListener listener) {
+            mListeners.remove(listener);
+        }
+
+        public DisplayController.Info getInfo() {
+            return mInfo;
+        }
+
+        protected void handleOnChange() {
+            Info oldInfo = mInfo;
+            Info info = new Info(mDisplayContext);
+
+            int change = 0;
+            if (info.hasDifferentSize(oldInfo)) {
+                change |= CHANGE_SIZE;
+            }
+            if (oldInfo.rotation != info.rotation) {
+                change |= CHANGE_ROTATION;
+            }
+            if (info.singleFrameMs != oldInfo.singleFrameMs) {
+                change |= CHANGE_FRAME_DELAY;
+            }
+
+            if (change != 0) {
+                mInfo = info;
+                final int flags = change;
+                MAIN_EXECUTOR.execute(() -> notifyChange(flags));
+            }
+        }
+
+        private void notifyChange(int flags) {
+            for (int i = mListeners.size() - 1; i >= 0; i--) {
+                mListeners.get(i).onDisplayInfoChanged(mInfo, flags);
+            }
+        }
+
+        private static DisplayHolder create(Context context, int id) {
+            DisplayManager dm = context.getSystemService(DisplayManager.class);
+            Display display = dm.getDisplay(id);
+            if (display == null) {
+                return null;
+            }
+            // Use application context to create display context so that it can have its own
+            // Resources.
+            Context displayContext = context.getApplicationContext().createDisplayContext(display);
+            return new DisplayHolder(displayContext);
+        }
+    }
+
+    public static class Info {
+
+        public final int id;
+        public final int rotation;
+        public final int singleFrameMs;
+
+        public final Point realSize;
+        public final Point smallestSize;
+        public final Point largestSize;
+
+        public final DisplayMetrics metrics;
+
+        @VisibleForTesting
+        public Info(int id, int rotation, int singleFrameMs, Point realSize, Point smallestSize,
+                Point largestSize, DisplayMetrics metrics) {
+            this.id = id;
+            this.rotation = rotation;
+            this.singleFrameMs = singleFrameMs;
+            this.realSize = realSize;
+            this.smallestSize = smallestSize;
+            this.largestSize = largestSize;
+            this.metrics = metrics;
+        }
+
+        private Info(Context context) {
+            this(context, context.getSystemService(DisplayManager.class)
+                    .getDisplay(DEFAULT_DISPLAY));
+        }
+
+        public Info(Context context, Display display) {
+            id = display.getDisplayId();
+            rotation = display.getRotation();
+
+            float refreshRate = display.getRefreshRate();
+            singleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
+
+            realSize = new Point();
+            smallestSize = new Point();
+            largestSize = new Point();
+            display.getRealSize(realSize);
+            display.getCurrentSizeRange(smallestSize, largestSize);
+
+            metrics = context.getResources().getDisplayMetrics();
+        }
+
+        private boolean hasDifferentSize(Info info) {
+            if (!realSize.equals(info.realSize)
+                    && !realSize.equals(info.realSize.y, info.realSize.x)) {
+                Log.d(TAG, String.format("Display size changed from %s to %s",
+                        info.realSize, realSize));
+                return true;
+            }
+
+            if (!smallestSize.equals(info.smallestSize) || !largestSize.equals(info.largestSize)) {
+                Log.d(TAG, String.format("Available size changed from [%s, %s] to [%s, %s]",
+                        smallestSize, largestSize, info.smallestSize, info.largestSize));
+                return true;
+            }
+
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index fcb96d7..1cec0ec 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -16,20 +16,19 @@
 
 package com.android.launcher3.util;
 
-import android.content.Context;
+import android.os.FileUtils;
 import android.util.Log;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.UUID;
 
 /**
  * Supports various IO utility functions
@@ -52,6 +51,9 @@
     }
 
     public static long copy(InputStream from, OutputStream to) throws IOException {
+        if (Utilities.ATLEAST_Q) {
+            return FileUtils.copy(from, to);
+        }
         byte[] buf = new byte[BUF_SIZE];
         long total = 0;
         int r;
@@ -62,25 +64,6 @@
         return total;
     }
 
-    /**
-     * Utility method to debug binary data
-     */
-    public static String createTempFile(Context context, byte[] data) {
-        if (!FeatureFlags.IS_STUDIO_BUILD) {
-            throw new IllegalStateException("Method only allowed in development mode");
-        }
-
-        String name = UUID.randomUUID().toString();
-        File file = new File(context.getCacheDir(), name);
-        try (FileOutputStream fo = new FileOutputStream(file)) {
-            fo.write(data);
-            fo.flush();
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-        return file.getAbsolutePath();
-    }
-
     public static void closeSilently(Closeable c) {
         if (c != null) {
             try {
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index e98af35..d26bb57 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 
 import java.util.HashSet;
+import java.util.Set;
 
 /**
  * A utility class to check for {@link ItemInfo}
@@ -99,7 +100,7 @@
         return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
     }
 
-    static ItemInfoMatcher ofShortcutKeys(HashSet<ShortcutKey> keys) {
+    static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
         return  (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
                         keys.contains(ShortcutKey.fromItemInfo(info));
     }
diff --git a/src/com/android/launcher3/util/MultiHashMap.java b/src/com/android/launcher3/util/MultiHashMap.java
deleted file mode 100644
index b7275c1..0000000
--- a/src/com/android/launcher3/util/MultiHashMap.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2016 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 java.util.ArrayList;
-import java.util.HashMap;
-
-/**
- * A utility map from keys to an ArrayList of values.
- */
-public class MultiHashMap<K, V> extends HashMap<K, ArrayList<V>> {
-
-    public MultiHashMap() { }
-
-    public MultiHashMap(int size) {
-        super(size);
-    }
-
-    public void addToList(K key, V value) {
-        ArrayList<V> list = get(key);
-        if (list == null) {
-            list = new ArrayList<>();
-            list.add(value);
-            put(key, list);
-        } else {
-            list.add(value);
-        }
-    }
-
-    @Override
-    public MultiHashMap<K, V> clone() {
-        MultiHashMap<K, V> map = new MultiHashMap<>(size());
-        for (Entry<K, ArrayList<V>> entry : entrySet()) {
-            map.put(entry.getKey(), new ArrayList<V>(entry.getValue()));
-        }
-        return map;
-    }
-}
diff --git a/src/com/android/launcher3/util/MultiValueAlpha.java b/src/com/android/launcher3/util/MultiValueAlpha.java
index a8642b0..5be9529 100644
--- a/src/com/android/launcher3/util/MultiValueAlpha.java
+++ b/src/com/android/launcher3/util/MultiValueAlpha.java
@@ -19,6 +19,8 @@
 import android.util.FloatProperty;
 import android.view.View;
 
+import com.android.launcher3.anim.AlphaUpdateListener;
+
 import java.util.Arrays;
 
 /**
@@ -44,6 +46,8 @@
     private final AlphaProperty[] mMyProperties;
 
     private int mValidMask;
+    // Whether we should change from INVISIBLE to VISIBLE and vice versa at low alpha values.
+    private boolean mUpdateVisibility;
 
     public MultiValueAlpha(View view, int size) {
         mView = view;
@@ -66,6 +70,11 @@
         return mMyProperties[index];
     }
 
+    /** Sets whether we should update between INVISIBLE and VISIBLE based on alpha. */
+    public void setUpdateVisibility(boolean updateVisibility) {
+        mUpdateVisibility = updateVisibility;
+    }
+
     public class AlphaProperty {
 
         private final int mMyMask;
@@ -99,6 +108,9 @@
             mValue = value;
 
             mView.setAlpha(mOthers * mValue);
+            if (mUpdateVisibility) {
+                AlphaUpdateListener.updateVisibility(mView);
+            }
         }
 
         public float getValue() {
diff --git a/src/com/android/launcher3/util/OnboardingPrefs.java b/src/com/android/launcher3/util/OnboardingPrefs.java
index 26313e5..1b33197 100644
--- a/src/com/android/launcher3/util/OnboardingPrefs.java
+++ b/src/com/android/launcher3/util/OnboardingPrefs.java
@@ -36,7 +36,6 @@
     public static final String SHELF_BOUNCE_SEEN = "launcher.shelf_bounce_seen";
     public static final String HOME_BOUNCE_COUNT = "launcher.home_bounce_count";
     public static final String SHELF_BOUNCE_COUNT = "launcher.shelf_bounce_count";
-    public static final String ALL_APPS_COUNT = "launcher.all_apps_count";
     public static final String HOTSEAT_DISCOVERY_TIP_COUNT = "launcher.hotseat_discovery_tip_count";
     public static final String HOTSEAT_LONGPRESS_TIP_SEEN = "launcher.hotseat_longpress_tip_seen";
 
@@ -45,7 +44,8 @@
      */
     @StringDef(value = {
             HOME_BOUNCE_SEEN,
-            SHELF_BOUNCE_SEEN
+            SHELF_BOUNCE_SEEN,
+            HOTSEAT_LONGPRESS_TIP_SEEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface EventBoolKey {}
@@ -56,7 +56,6 @@
     @StringDef(value = {
             HOME_BOUNCE_COUNT,
             SHELF_BOUNCE_COUNT,
-            ALL_APPS_COUNT,
             HOTSEAT_DISCOVERY_TIP_COUNT
     })
     @Retention(RetentionPolicy.SOURCE)
@@ -67,7 +66,6 @@
         Map<String, Integer> maxCounts = new ArrayMap<>(4);
         maxCounts.put(HOME_BOUNCE_COUNT, 3);
         maxCounts.put(SHELF_BOUNCE_COUNT, 3);
-        maxCounts.put(ALL_APPS_COUNT, 5);
         maxCounts.put(HOTSEAT_DISCOVERY_TIP_COUNT, 5);
         MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
     }
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 86f3431..523545a 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -37,7 +37,6 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PatternMatcher;
-import android.os.Process;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -46,7 +45,6 @@
 
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -96,36 +94,12 @@
      * Returns the application info for the provided package or null
      */
     public ApplicationInfo getApplicationInfo(String packageName, UserHandle user, int flags) {
-        if (Utilities.ATLEAST_OREO) {
-            try {
-                ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
-                return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
-                        ? null : info;
-            } catch (PackageManager.NameNotFoundException e) {
-                return null;
-            }
-        } else {
-            final boolean isPrimaryUser = Process.myUserHandle().equals(user);
-            if (!isPrimaryUser && (flags == 0)) {
-                // We are looking for an installed app on a secondary profile. Prior to O, the only
-                // entry point for work profiles is through the LauncherActivity.
-                List<LauncherActivityInfo> activityList =
-                        mLauncherApps.getActivityList(packageName, user);
-                return activityList.size() > 0 ? activityList.get(0).getApplicationInfo() : null;
-            }
-            try {
-                ApplicationInfo info = mPm.getApplicationInfo(packageName, flags);
-                // There is no way to check if the app is installed for managed profile. But for
-                // primary profile, we can still have this check.
-                if (isPrimaryUser && ((info.flags & ApplicationInfo.FLAG_INSTALLED) == 0)
-                        || !info.enabled) {
-                    return null;
-                }
-                return info;
-            } catch (PackageManager.NameNotFoundException e) {
-                // Package not found
-                return null;
-            }
+        try {
+            ApplicationInfo info = mLauncherApps.getApplicationInfo(packageName, flags, user);
+            return (info.flags & ApplicationInfo.FLAG_INSTALLED) == 0 || !info.enabled
+                    ? null : info;
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
         }
     }
 
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
new file mode 100644
index 0000000..7ff2abb
--- /dev/null
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -0,0 +1,184 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.LongFunction;
+
+/**
+ * Utility class to read/write a list of {@link com.android.launcher3.model.data.ItemInfo} on disk.
+ * This class is not thread safe, the caller should ensure proper threading
+ */
+public class PersistedItemArray<T extends ItemInfo> {
+
+    private static final String TAG = "PersistedItemArray";
+
+    private static final String TAG_ROOT = "items";
+    private static final String TAG_ENTRY = "entry";
+
+    private final String mFileName;
+
+    public PersistedItemArray(String fileName) {
+        mFileName = fileName + ".xml";
+    }
+
+    /**
+     * Writes the provided list of items on the disk
+     */
+    @WorkerThread
+    public void write(Context context, List<T> items) {
+        AtomicFile file = getFile(context);
+        FileOutputStream fos;
+        try {
+            fos = file.startWrite();
+        } catch (IOException e) {
+            Log.e(TAG, "Unable to persist items in " + mFileName, e);
+            return;
+        }
+
+        UserCache userCache = UserCache.INSTANCE.get(context);
+
+        try {
+            XmlSerializer out = Xml.newSerializer();
+            out.setOutput(fos, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_ROOT);
+            for (T item : items) {
+                Intent intent = item.getIntent();
+                if (intent == null) {
+                    continue;
+                }
+
+                out.startTag(null, TAG_ENTRY);
+                out.attribute(null, Favorites.ITEM_TYPE, Integer.toString(item.itemType));
+                out.attribute(null, Favorites.PROFILE_ID,
+                        Long.toString(userCache.getSerialNumberForUser(item.user)));
+                out.attribute(null, Favorites.INTENT, intent.toUri(0));
+                out.endTag(null, TAG_ENTRY);
+            }
+            out.endTag(null, TAG_ROOT);
+            out.endDocument();
+        } catch (IOException e) {
+            file.failWrite(fos);
+            Log.e(TAG, "Unable to persist items in " + mFileName, e);
+            return;
+        }
+
+        file.finishWrite(fos);
+    }
+
+    /**
+     * Reads the items from the disk
+     */
+    @WorkerThread
+    public List<T> read(Context context, ItemFactory<T> factory) {
+        return read(context, factory, UserCache.INSTANCE.get(context)::getUserForSerialNumber);
+    }
+
+    /**
+     * Reads the items from the disk
+     * @param userFn method to provide user handle for a given user serial
+     */
+    @WorkerThread
+    public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
+        List<T> result = new ArrayList<>();
+        try (FileInputStream fis = getFile(context).openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
+
+            AutoInstallsLayout.beginDocument(parser, TAG_ROOT);
+            final int depth = parser.getDepth();
+
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG
+                    || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG || !TAG_ENTRY.equals(parser.getName())) {
+                    continue;
+                }
+                try {
+                    int itemType = Integer.parseInt(
+                            parser.getAttributeValue(null, Favorites.ITEM_TYPE));
+                    UserHandle user = userFn.apply(Long.parseLong(
+                            parser.getAttributeValue(null, Favorites.PROFILE_ID)));
+                    Intent intent = Intent.parseUri(
+                            parser.getAttributeValue(null, Favorites.INTENT), 0);
+
+                    if (user != null && intent != null) {
+                        T item = factory.createInfo(itemType, user, intent);
+                        if (item != null) {
+                            result.add(item);
+                        }
+                    }
+                } catch (Exception e) {
+                    // Ignore this entry
+                }
+            }
+        } catch (FileNotFoundException e) {
+            // Ignore
+        } catch (IOException | XmlPullParserException e) {
+            Log.e(TAG, "Unable to read items in " + mFileName, e);
+            return Collections.emptyList();
+        }
+        return result;
+    }
+
+    /**
+     * Returns the underlying file used for persisting data
+     */
+    public AtomicFile getFile(Context context) {
+        return new AtomicFile(context.getFileStreamPath(mFileName));
+    }
+
+    /**
+     * Interface to create an ItemInfo during parsing
+     */
+    public interface ItemFactory<T extends ItemInfo> {
+
+        /**
+         * Returns an item info or null in which case the entry is ignored
+         */
+        @Nullable
+        T createInfo(int itemType, UserHandle user, Intent intent);
+    }
+}
diff --git a/src/com/android/launcher3/util/SystemUiController.java b/src/com/android/launcher3/util/SystemUiController.java
index 275c024..50166c3 100644
--- a/src/com/android/launcher3/util/SystemUiController.java
+++ b/src/com/android/launcher3/util/SystemUiController.java
@@ -19,8 +19,6 @@
 import android.view.View;
 import android.view.Window;
 
-import com.android.launcher3.Utilities;
-
 import java.util.Arrays;
 
 /**
@@ -33,6 +31,7 @@
     public static final int UI_STATE_SCRIM_VIEW = 1;
     public static final int UI_STATE_WIDGET_BOTTOM_SHEET = 2;
     public static final int UI_STATE_OVERVIEW = 3;
+    public static final int UI_STATE_ALLAPPS = 4;
 
     public static final int FLAG_LIGHT_NAV = 1 << 0;
     public static final int FLAG_DARK_NAV = 1 << 1;
@@ -40,7 +39,7 @@
     public static final int FLAG_DARK_STATUS = 1 << 3;
 
     private final Window mWindow;
-    private final int[] mStates = new int[4];
+    private final int[] mStates = new int[5];
 
     public SystemUiController(Window window) {
         mWindow = window;
@@ -77,12 +76,10 @@
     }
 
     private int getSysUiVisibilityFlags(int stateFlag, int currentVisibility) {
-        if (Utilities.ATLEAST_OREO) {
-            if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
-                currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
-            } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
-                currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
-            }
+        if ((stateFlag & FLAG_LIGHT_NAV) != 0) {
+            currentVisibility |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+        } else if ((stateFlag & FLAG_DARK_NAV) != 0) {
+            currentVisibility &= ~(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
         }
 
         if ((stateFlag & FLAG_LIGHT_STATUS) != 0) {
diff --git a/src/com/android/launcher3/util/VelocityUtils.java b/src/com/android/launcher3/util/VelocityUtils.java
new file mode 100644
index 0000000..d5962ed
--- /dev/null
+++ b/src/com/android/launcher3/util/VelocityUtils.java
@@ -0,0 +1,28 @@
+/*
+ * 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.util;
+
+/**
+ * Contains some constants and functions to standardize velocity usage.
+ */
+public class VelocityUtils {
+
+    /**
+     * Unit to pass to {@link android.view.VelocityTracker#computeCurrentVelocity(int)}.
+     */
+    public static final int PX_PER_MS = 1;
+
+}
diff --git a/src/com/android/launcher3/views/AccessibilityActionsView.java b/src/com/android/launcher3/views/AccessibilityActionsView.java
new file mode 100644
index 0000000..0eacaa3
--- /dev/null
+++ b/src/com/android/launcher3/views/AccessibilityActionsView.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.views;
+
+import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.LauncherState.NORMAL;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.views.OptionsPopupView.OptionItem;
+
+/**
+ * Placeholder view to expose additional Launcher actions via accessibility actions
+ */
+public class AccessibilityActionsView extends View implements StateListener<LauncherState> {
+
+    public AccessibilityActionsView(Context context) {
+        this(context, null);
+    }
+
+    public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public AccessibilityActionsView(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        Launcher.getLauncher(context).getStateManager().addStateListener(this);
+        setWillNotDraw(true);
+    }
+
+    @Override
+    public void onStateTransitionComplete(LauncherState finalState) {
+        setImportantForAccessibility(finalState == NORMAL
+                ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO);
+    }
+
+    @Override
+    public AccessibilityNodeInfo createAccessibilityNodeInfo() {
+        AccessibilityNodeInfo info = super.createAccessibilityNodeInfo();
+        Launcher l = Launcher.getLauncher(getContext());
+        info.addAction(new AccessibilityAction(
+                R.string.all_apps_button_label, l.getText(R.string.all_apps_button_label)));
+        for (OptionItem item : OptionsPopupView.getOptions(l)) {
+            info.addAction(new AccessibilityAction(item.labelRes, l.getText(item.labelRes)));
+        }
+        return info;
+    }
+
+    @Override
+    public boolean performAccessibilityAction(int action, Bundle arguments) {
+        if (super.performAccessibilityAction(action, arguments)) {
+            return true;
+        }
+        Launcher l = Launcher.getLauncher(getContext());
+        if (action == R.string.all_apps_button_label) {
+            l.getStateManager().goToState(ALL_APPS);
+            return true;
+        }
+        for (OptionItem item : OptionsPopupView.getOptions(l)) {
+            if (item.labelRes == action) {
+                if (item.eventId.getId() > 0) {
+                    l.getStatsLogManager().logger().log(item.eventId);
+                }
+                if (item.clickListener.onLongClick(this)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index b4a6b14..1f12a2f 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -87,10 +87,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 357eeb8..2be827b 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -20,7 +20,7 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
+import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.annotation.TargetApi;
 import android.app.WallpaperInfo;
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 8186dfa..52a82f8 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -256,7 +256,6 @@
         Drawable drawable = null;
         Drawable badge = null;
         boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
-                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                 && !info.isDisabled(); // Use original icon for disabled icons.
         Drawable btvIcon = originalView instanceof BubbleTextView
                 ? ((BubbleTextView) originalView).getIcon() : null;
@@ -383,8 +382,7 @@
     @WorkerThread
     @SuppressWarnings("WrongThread")
     private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O
-                || !(drawable instanceof AdaptiveIconDrawable)
+        if (!(drawable instanceof AdaptiveIconDrawable)
                 || (drawable instanceof FolderAdaptiveIcon)) {
             return 0;
         }
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 040619e..011f6de 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -40,7 +40,7 @@
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
 
 /**
@@ -97,7 +97,7 @@
 
         // Remove after some time, to avoid flickering
         Executors.MAIN_EXECUTOR.getHandler().postDelayed(mRemoveViewRunnable,
-                DefaultDisplay.INSTANCE.get(mLauncher).getInfo().singleFrameMs);
+                DisplayController.getDefaultDisplay(mLauncher).getInfo().singleFrameMs);
     }
 
     private void removeViewFromParent() {
@@ -122,9 +122,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) { }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ICON_SURFACE) != 0;
     }
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
new file mode 100644
index 0000000..a8e1c6b
--- /dev/null
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.graphics.DragPreviewProvider;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.util.List;
+
+/**
+ * A view representing a high confidence app search result that includes shortcuts
+ */
+public class HeroSearchResultView extends LinearLayout implements DragSource,
+        AllAppsSearchBarController.PayloadResultHandler<List<ItemInfoWithIcon>> {
+
+    public static final int MAX_SHORTCUTS_COUNT = 2;
+    BubbleTextView mBubbleTextView;
+    View mIconView;
+    BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
+    AllAppsSearchPlugin mPlugin;
+
+    public HeroSearchResultView(Context context) {
+        super(context);
+    }
+
+    public HeroSearchResultView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public HeroSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Launcher launcher = Launcher.getLauncher(getContext());
+        DeviceProfile grid = launcher.getDeviceProfile();
+        mIconView = findViewById(R.id.icon);
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = grid.allAppsIconSizePx;
+        iconParams.width = grid.allAppsIconSizePx;
+
+
+        mBubbleTextView = findViewById(R.id.bubble_text);
+        mBubbleTextView.setOnClickListener(view -> {
+            handleSelection(SearchTargetEvent.SELECT);
+            launcher.getItemOnClickListener().onClick(view);
+        });
+        mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this));
+        setLayoutParams(
+                new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, grid.allAppsCellHeightPx));
+
+
+        mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0);
+        mDeepShortcutTextViews[1] = findViewById(R.id.shortcut_1);
+        for (BubbleTextView bubbleTextView : mDeepShortcutTextViews) {
+            bubbleTextView.setLayoutParams(
+                    new LinearLayout.LayoutParams(grid.allAppsIconSizePx,
+                            grid.allAppsIconSizePx));
+            bubbleTextView.setOnClickListener(view -> {
+                WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
+                SearchTargetEvent event = new SearchTargetEvent(
+                        SearchTarget.ItemType.APP_HERO,
+                        SearchTargetEvent.CHILD_SELECT);
+                event.bundle = getAppBundle(itemInfo);
+                event.bundle.putString("shortcut_id", itemInfo.getDeepShortcutId());
+                if (mPlugin != null) {
+                    mPlugin.notifySearchTargetEvent(event);
+                }
+                launcher.getItemOnClickListener().onClick(view);
+            });
+        }
+    }
+
+    /**
+     * Apply {@link ItemInfo} for appIcon and shortcut Icons
+     */
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<List<ItemInfoWithIcon>> adapterItem) {
+        mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
+        mIconView.setBackground(mBubbleTextView.getIcon());
+        mIconView.setTag(adapterItem.appInfo);
+        List<ItemInfoWithIcon> shorcutInfos = adapterItem.getPayload();
+        for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
+            mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
+            if (i < shorcutInfos.size()) {
+                mDeepShortcutTextViews[i].applyFromItemInfoWithIcon(shorcutInfos.get(i));
+            }
+        }
+        mPlugin = adapterItem.getPlugin();
+        adapterItem.setSelectionHandler(this::handleSelection);
+    }
+
+    @Override
+    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
+        mBubbleTextView.setVisibility(VISIBLE);
+        mBubbleTextView.setIconVisible(true);
+    }
+
+    private void setWillDrawIcon(boolean willDraw) {
+        mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    /**
+     * Drag and drop handler for popup items in Launcher activity
+     */
+    public static class HeroItemDragHandler implements OnLongClickListener {
+        private final Launcher mLauncher;
+        private final HeroSearchResultView mContainer;
+
+        HeroItemDragHandler(Context context, HeroSearchResultView container) {
+            mLauncher = Launcher.getLauncher(context);
+            mContainer = container;
+        }
+
+        @Override
+        public boolean onLongClick(View v) {
+            if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+            mContainer.setWillDrawIcon(false);
+
+            DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
+            WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((AppInfo) v.getTag());
+            itemInfo.container = CONTAINER_ALL_APPS;
+            DragPreviewProvider previewProvider = new ShortcutDragPreviewProvider(
+                    mContainer.mIconView, new Point());
+            mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
+                    draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
+
+            SearchTargetEvent event = new SearchTargetEvent(
+                    SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
+            event.bundle = getAppBundle(itemInfo);
+            if (mContainer.mPlugin != null) {
+                mContainer.mPlugin.notifySearchTargetEvent(event);
+            }
+
+            return false;
+        }
+    }
+
+    private void handleSelection(int eventType) {
+        ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag();
+        if (itemInfo == null) return;
+        Launcher launcher = Launcher.getLauncher(getContext());
+        launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
+
+        SearchTargetEvent event = new SearchTargetEvent(
+                SearchTarget.ItemType.APP_HERO, eventType);
+        event.bundle = getAppBundle(itemInfo);
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(event);
+        }
+    }
+
+    /**
+     * Helper method to generate {@link SearchTargetEvent} bundle from {@link ItemInfo}
+     */
+    public static Bundle getAppBundle(ItemInfo itemInfo) {
+        Bundle b = new Bundle();
+        b.putParcelable(Intent.EXTRA_COMPONENT_NAME, itemInfo.getTargetComponent());
+        b.putParcelable(Intent.EXTRA_USER, itemInfo.user);
+        return b;
+    }
+}
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
index 3ef778b..6e3f0ce 100644
--- a/src/com/android/launcher3/views/ListenerView.java
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -85,11 +85,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Users do not interact with FloatingIconView, so there is nothing to log here.
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_LISTENER) != 0;
     }
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 049a1ac..3ec20d5 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -85,10 +85,10 @@
         if (item == null) {
             return false;
         }
-        if (item.mEventId.getId() > 0) {
-            mLauncher.getStatsLogManager().logger().log(item.mEventId);
+        if (item.eventId.getId() > 0) {
+            mLauncher.getStatsLogManager().logger().log(item.eventId);
         }
-        if (item.mClickListener.onLongClick(view)) {
+        if (item.clickListener.onLongClick(view)) {
             close(true);
             return true;
         }
@@ -108,11 +108,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO:
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_OPTIONS_POPUP) != 0;
     }
@@ -130,8 +125,8 @@
         for (OptionItem item : items) {
             DeepShortcutView view =
                     (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
-            view.getIconView().setBackgroundResource(item.mIconRes);
-            view.getBubbleText().setText(item.mLabelRes);
+            view.getIconView().setBackgroundResource(item.iconRes);
+            view.getBubbleText().setText(item.labelRes);
             view.setDividerVisibility(View.INVISIBLE);
             view.setOnClickListener(popup);
             view.setOnLongClickListener(popup);
@@ -152,7 +147,13 @@
             y = launcher.getDragLayer().getHeight() / 2;
         }
         RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
+        show(launcher, target, getOptions(launcher));
+    }
 
+    /**
+     * Returns the list of supported actions
+     */
+    public static ArrayList<OptionItem> getOptions(Launcher launcher) {
         ArrayList<OptionItem> options = new ArrayList<>();
         int resString = Utilities.existsStyleWallpapers(launcher) ?
                 R.string.styles_wallpaper_button_text : R.string.wallpaper_button_text;
@@ -170,10 +171,10 @@
                 LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS,
                 OptionsPopupView::startSettings));
 
-        show(launcher, target, options);
+        return options;
     }
 
-    public static boolean onWidgetsClicked(View view) {
+    private static boolean onWidgetsClicked(View view) {
         return openWidgets(Launcher.getLauncher(view.getContext())) != null;
     }
 
@@ -188,7 +189,7 @@
         }
     }
 
-    public static boolean startSettings(View view) {
+    private static boolean startSettings(View view) {
         TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: startSettings");
         Launcher launcher = Launcher.getLauncher(view.getContext());
         launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES)
@@ -201,7 +202,7 @@
      * Event handler for the wallpaper picker button that appears after a long press
      * on the home screen.
      */
-    public static boolean startWallpaperPicker(View v) {
+    private static boolean startWallpaperPicker(View v) {
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!Utilities.isWallpaperAllowed(launcher)) {
             Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show();
@@ -220,30 +221,30 @@
         if (!TextUtils.isEmpty(pickerPackage)) {
             intent.setPackage(pickerPackage);
         }
-        return launcher.startActivitySafely(v, intent, dummyInfo(intent));
+        return launcher.startActivitySafely(v, intent, placeholderInfo(intent));
     }
 
-    static WorkspaceItemInfo dummyInfo(Intent intent) {
-        WorkspaceItemInfo dummyInfo = new WorkspaceItemInfo();
-        dummyInfo.intent = intent;
-        dummyInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
-        dummyInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
-        return dummyInfo;
+    static WorkspaceItemInfo placeholderInfo(Intent intent) {
+        WorkspaceItemInfo placeholderInfo = new WorkspaceItemInfo();
+        placeholderInfo.intent = intent;
+        placeholderInfo.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+        placeholderInfo.container = LauncherSettings.Favorites.CONTAINER_SETTINGS;
+        return placeholderInfo;
     }
 
     public static class OptionItem {
 
-        private final int mLabelRes;
-        private final int mIconRes;
-        private final EventEnum mEventId;
-        private final OnLongClickListener mClickListener;
+        public final int labelRes;
+        public final int iconRes;
+        public final EventEnum eventId;
+        public final OnLongClickListener clickListener;
 
         public OptionItem(int labelRes, int iconRes, EventEnum eventId,
                 OnLongClickListener clickListener) {
-            mLabelRes = labelRes;
-            mIconRes = iconRes;
-            mEventId = eventId;
-            mClickListener = clickListener;
+            this.labelRes = labelRes;
+            this.iconRes = iconRes;
+            this.eventId = eventId;
+            this.clickListener = clickListener;
         }
     }
 }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 22faf97..77cec80 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -15,119 +15,39 @@
  */
 package com.android.launcher3.views;
 
-import static android.content.Context.ACCESSIBILITY_SERVICE;
-import static android.view.MotionEvent.ACTION_DOWN;
-
 import static androidx.core.graphics.ColorUtils.compositeColors;
 
-import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
-import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.Keyframe;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
-import android.animation.RectEvaluator;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Color;
-import android.graphics.Point;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.util.AttributeSet;
-import android.util.IntProperty;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.core.graphics.ColorUtils;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
 
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StateManager.StateListener;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.uioverrides.WallpaperColorInfo.OnChangeListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.widget.WidgetsFullSheet;
-
-import java.util.List;
 
 /**
  * Simple scrim which draws a flat color
  */
-public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener,
-        AccessibilityStateChangeListener {
+public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
 
-    public static final IntProperty<ScrimView> DRAG_HANDLE_ALPHA =
-            new IntProperty<ScrimView>("dragHandleAlpha") {
-
-                @Override
-                public Integer get(ScrimView scrimView) {
-                    return scrimView.mDragHandleAlpha;
-                }
-
-                @Override
-                public void setValue(ScrimView scrimView, int value) {
-                    scrimView.setDragHandleAlpha(value);
-                }
-            };
-    private static final int WALLPAPERS = R.string.wallpaper_button_text;
-    private static final int WIDGETS = R.string.widget_button_text;
-    private static final int SETTINGS = R.string.settings_button_text;
-    private static final int ALPHA_CHANNEL_COUNT = 1;
-
-    private static final long DRAG_HANDLE_BOUNCE_DURATION_MS = 300;
-    // How much to delay before repeating the bounce.
-    private static final long DRAG_HANDLE_BOUNCE_DELAY_MS = 200;
-    // Repeat this many times (i.e. total number of bounces is 1 + this).
-    private static final int DRAG_HANDLE_BOUNCE_REPEAT_COUNT = 2;
-
-    private final Rect mTempRect = new Rect();
-    private final int[] mTempPos = new int[2];
-
+    private static final float SCRIM_ALPHA = .75f;
     protected final T mLauncher;
     private final WallpaperColorInfo mWallpaperColorInfo;
-    private final AccessibilityManager mAM;
     protected final int mEndScrim;
     protected final boolean mIsScrimDark;
 
-    private final StateListener<LauncherState> mAccessibilityLauncherStateListener =
-            new StateListener<LauncherState>() {
-        @Override
-        public void onStateTransitionComplete(LauncherState finalState) {
-            setImportantForAccessibility(finalState == ALL_APPS
-                    ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                    : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-        }
-    };
-
     protected float mMaxScrimAlpha;
 
     protected float mProgress = 1;
@@ -137,83 +57,36 @@
     protected int mEndFlatColor;
     protected int mEndFlatColorAlpha;
 
-    protected final Point mDragHandleSize;
-    private final int mDragHandleTouchSize;
-    private final int mDragHandlePaddingInVerticalBarLayout;
-    protected float mDragHandleOffset;
-    private final Rect mDragHandleBounds;
-    private final RectF mHitRect = new RectF();
-    private ObjectAnimator mDragHandleAnim;
-
-    private final MultiValueAlpha mMultiValueAlpha;
-
-    private final AccessibilityHelper mAccessibilityHelper;
-    @Nullable
-    protected Drawable mDragHandle;
-
-    private int mDragHandleAlpha = 255;
-
     public ScrimView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mLauncher = Launcher.cast(Launcher.getLauncher(context));
         mWallpaperColorInfo = WallpaperColorInfo.INSTANCE.get(context);
-        mEndScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        int endScrim = Themes.getAttrColor(context, R.attr.allAppsScrimColor);
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            endScrim = ColorUtils.setAlphaComponent(endScrim, (int) (255  * SCRIM_ALPHA));
+        }
+        mEndScrim = endScrim;
         mIsScrimDark = ColorUtils.calculateLuminance(mEndScrim) < 0.5f;
 
         mMaxScrimAlpha = 0.7f;
-
-        Resources res = context.getResources();
-        mDragHandleSize = new Point(res.getDimensionPixelSize(R.dimen.vertical_drag_handle_width),
-                res.getDimensionPixelSize(R.dimen.vertical_drag_handle_height));
-        mDragHandleBounds = new Rect(0, 0, mDragHandleSize.x, mDragHandleSize.y);
-        mDragHandleTouchSize = res.getDimensionPixelSize(R.dimen.vertical_drag_handle_touch_size);
-        mDragHandlePaddingInVerticalBarLayout = context.getResources()
-                .getDimensionPixelSize(R.dimen.vertical_drag_handle_padding_in_vertical_bar_layout);
-
-        mAccessibilityHelper = createAccessibilityHelper();
-        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
-
-        mAM = (AccessibilityManager) context.getSystemService(ACCESSIBILITY_SERVICE);
         setFocusable(false);
-        mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
-    }
-
-    public AlphaProperty getAlphaProperty(int index) {
-        return mMultiValueAlpha.getProperty(index);
-    }
-
-    @NonNull
-    protected AccessibilityHelper createAccessibilityHelper() {
-        return new AccessibilityHelper();
     }
 
     @Override
-    public void setInsets(Rect insets) {
-        updateDragHandleBounds();
-        updateDragHandleVisibility();
-    }
+    public void setInsets(Rect insets) { }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        updateDragHandleBounds();
-    }
 
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mWallpaperColorInfo.addOnChangeListener(this);
         onExtractedColorsChanged(mWallpaperColorInfo);
-
-        mAM.addAccessibilityStateChangeListener(this);
-        onAccessibilityStateChanged(mAM.isEnabled());
     }
 
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mWallpaperColorInfo.removeOnChangeListener(this);
-        mAM.removeAccessibilityStateChangeListener(this);
     }
 
     @Override
@@ -234,10 +107,8 @@
     public void setProgress(float progress) {
         if (mProgress != progress) {
             mProgress = progress;
-            stopDragHandleEducationAnim();
             updateColors();
             updateSysUiColors();
-            updateDragHandleAlpha();
             invalidate();
         }
     }
@@ -260,286 +131,10 @@
         }
     }
 
-    protected void updateDragHandleAlpha() {
-        if (mDragHandle != null) {
-            mDragHandle.setAlpha(mDragHandleAlpha);
-        }
-    }
-
-    private void setDragHandleAlpha(int alpha) {
-        if (alpha != mDragHandleAlpha) {
-            mDragHandleAlpha = alpha;
-            if (mDragHandle != null) {
-                mDragHandle.setAlpha(mDragHandleAlpha);
-                invalidate();
-            }
-        }
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         if (mCurrentFlatColor != 0) {
             canvas.drawColor(mCurrentFlatColor);
         }
-        drawDragHandle(canvas);
-    }
-
-    protected void drawDragHandle(Canvas canvas) {
-        if (mDragHandle != null) {
-            canvas.translate(0, -mDragHandleOffset);
-            mDragHandle.draw(canvas);
-            canvas.translate(0, mDragHandleOffset);
-        }
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        boolean superHandledTouch = super.onTouchEvent(event);
-        if (event.getAction() == ACTION_DOWN) {
-            if (!superHandledTouch && mHitRect.contains(event.getX(), event.getY())) {
-                if (startDragHandleEducationAnim()) {
-                    return true;
-                }
-            }
-            stopDragHandleEducationAnim();
-        }
-        return superHandledTouch;
-    }
-
-    /**
-     * Animates the drag handle to demonstrate how to get to all apps.
-     * @return Whether the animation was started (false if drag handle is invisible).
-     */
-    public boolean startDragHandleEducationAnim() {
-        stopDragHandleEducationAnim();
-
-        if (mDragHandle == null || mDragHandle.getAlpha() != 255) {
-            return false;
-        }
-
-        final Drawable drawable = mDragHandle;
-        mDragHandle = null;
-
-        Rect bounds = new Rect(mDragHandleBounds);
-        bounds.offset(0, -(int) mDragHandleOffset);
-        drawable.setBounds(bounds);
-
-        Rect topBounds = new Rect(bounds);
-        topBounds.offset(0, -bounds.height());
-
-        Rect invalidateRegion = new Rect(bounds);
-        invalidateRegion.top = topBounds.top;
-
-        final float progressToReachTop = 0.6f;
-        Keyframe frameTop = Keyframe.ofObject(progressToReachTop, topBounds);
-        frameTop.setInterpolator(DEACCEL);
-        Keyframe frameBot = Keyframe.ofObject(1, bounds);
-        frameBot.setInterpolator(ACCEL_DEACCEL);
-        PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("bounds",
-                Keyframe.ofObject(0, bounds), frameTop, frameBot);
-        holder.setEvaluator(new RectEvaluator());
-
-        mDragHandleAnim = ObjectAnimator.ofPropertyValuesHolder(drawable, holder);
-        long totalBounceDuration = DRAG_HANDLE_BOUNCE_DURATION_MS + DRAG_HANDLE_BOUNCE_DELAY_MS;
-        // The bounce finishes by this progress, the rest of the duration just delays next bounce.
-        float delayStartProgress = 1f - (float) DRAG_HANDLE_BOUNCE_DELAY_MS / totalBounceDuration;
-        mDragHandleAnim.addUpdateListener((v) -> invalidate(invalidateRegion));
-        mDragHandleAnim.setDuration(totalBounceDuration);
-        mDragHandleAnim.setInterpolator(clampToProgress(LINEAR, 0, delayStartProgress));
-        mDragHandleAnim.setRepeatCount(DRAG_HANDLE_BOUNCE_REPEAT_COUNT);
-        getOverlay().add(drawable);
-
-        mDragHandleAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mDragHandleAnim = null;
-                getOverlay().remove(drawable);
-                updateDragHandleVisibility(drawable);
-            }
-        });
-        mDragHandleAnim.start();
-        return true;
-    }
-
-    private void stopDragHandleEducationAnim() {
-        if (mDragHandleAnim != null) {
-            mDragHandleAnim.end();
-        }
-    }
-
-    protected void updateDragHandleBounds() {
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        final int left;
-        final int width = getMeasuredWidth();
-        final int top = getMeasuredHeight() - mDragHandleSize.y - grid.getInsets().bottom;
-        final int topMargin;
-
-        if (grid.isVerticalBarLayout()) {
-            topMargin = grid.workspacePadding.bottom + mDragHandlePaddingInVerticalBarLayout;
-            if (grid.isSeascape()) {
-                left = width - grid.getInsets().right - mDragHandleSize.x
-                        - mDragHandlePaddingInVerticalBarLayout;
-            } else {
-                left = grid.getInsets().left + mDragHandlePaddingInVerticalBarLayout;
-            }
-        } else {
-            left = Math.round((width - mDragHandleSize.x) / 2f);
-            topMargin = grid.hotseatBarSizePx;
-        }
-        mDragHandleBounds.offsetTo(left, top - topMargin);
-        mHitRect.set(mDragHandleBounds);
-        // Inset outwards to increase touch size.
-        mHitRect.inset((mDragHandleSize.x - mDragHandleTouchSize) / 2f,
-                (mDragHandleSize.y - mDragHandleTouchSize) / 2f);
-
-        if (mDragHandle != null) {
-            mDragHandle.setBounds(mDragHandleBounds);
-        }
-    }
-
-    @Override
-    public void onAccessibilityStateChanged(boolean enabled) {
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
-        stateManager.removeStateListener(mAccessibilityLauncherStateListener);
-
-        if (enabled) {
-            stateManager.addStateListener(mAccessibilityLauncherStateListener);
-            mAccessibilityLauncherStateListener.onStateTransitionComplete(stateManager.getState());
-        } else {
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-        }
-        updateDragHandleVisibility();
-    }
-
-    public void updateDragHandleVisibility() {
-        updateDragHandleVisibility(null);
-    }
-
-    private void updateDragHandleVisibility(@Nullable Drawable recycle) {
-        boolean visible = shouldDragHandleBeVisible();
-        boolean wasVisible = mDragHandle != null;
-        if (visible != wasVisible) {
-            if (visible) {
-                mDragHandle = recycle != null ? recycle :
-                        mLauncher.getDrawable(R.drawable.drag_handle_indicator_shadow);
-                mDragHandle.setBounds(mDragHandleBounds);
-
-                updateDragHandleAlpha();
-            } else {
-                mDragHandle = null;
-            }
-            invalidate();
-        }
-    }
-
-    protected boolean shouldDragHandleBeVisible() {
-        return mLauncher.getDeviceProfile().isVerticalBarLayout() || mAM.isEnabled();
-    }
-
-    @Override
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        return mAccessibilityHelper.dispatchHoverEvent(event) || super.dispatchHoverEvent(event);
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent event) {
-        return mAccessibilityHelper.dispatchKeyEvent(event) || super.dispatchKeyEvent(event);
-    }
-
-    @Override
-    public void onFocusChanged(boolean gainFocus, int direction,
-            Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        mAccessibilityHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-    }
-
-    protected class AccessibilityHelper extends ExploreByTouchHelper {
-
-        private static final int DRAG_HANDLE_ID = 1;
-
-        public AccessibilityHelper() {
-            super(ScrimView.this);
-        }
-
-        @Override
-        protected int getVirtualViewAt(float x, float y) {
-            return  mHitRect.contains((int) x, (int) y)
-                    ? DRAG_HANDLE_ID : INVALID_ID;
-        }
-
-        @Override
-        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-            virtualViewIds.add(DRAG_HANDLE_ID);
-        }
-
-        @Override
-        protected void onPopulateNodeForVirtualView(int virtualViewId,
-                AccessibilityNodeInfoCompat node) {
-            node.setContentDescription(getContext().getString(R.string.all_apps_button_label));
-            node.setBoundsInParent(mDragHandleBounds);
-
-            getLocationOnScreen(mTempPos);
-            mTempRect.set(mDragHandleBounds);
-            mTempRect.offset(mTempPos[0], mTempPos[1]);
-            node.setBoundsInScreen(mTempRect);
-
-            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-            node.setClickable(true);
-            node.setFocusable(true);
-
-            if (mLauncher.isInState(NORMAL)) {
-                Context context = getContext();
-                if (Utilities.isWallpaperAllowed(context)) {
-                    node.addAction(
-                            new AccessibilityActionCompat(WALLPAPERS, context.getText(WALLPAPERS)));
-                }
-                node.addAction(new AccessibilityActionCompat(WIDGETS, context.getText(WIDGETS)));
-                node.addAction(new AccessibilityActionCompat(SETTINGS, context.getText(SETTINGS)));
-            }
-        }
-
-        @Override
-        protected boolean onPerformActionForVirtualView(
-                int virtualViewId, int action, Bundle arguments) {
-            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-                mLauncher.getUserEventDispatcher().logActionOnControl(
-                        Action.Touch.TAP, ControlType.ALL_APPS_BUTTON,
-                        mLauncher.getStateManager().getState().containerType);
-                mLauncher.getStateManager().goToState(ALL_APPS);
-                return true;
-            } else if (action == WALLPAPERS) {
-                return OptionsPopupView.startWallpaperPicker(ScrimView.this);
-            } else if (action == WIDGETS) {
-                int originalImportanceForAccessibility = getImportantForAccessibility();
-                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-                WidgetsFullSheet widgetsFullSheet = OptionsPopupView.openWidgets(mLauncher);
-                if (widgetsFullSheet == null) {
-                    setImportantForAccessibility(originalImportanceForAccessibility);
-                    return false;
-                }
-                widgetsFullSheet.addOnAttachStateChangeListener(new OnAttachStateChangeListener() {
-                    @Override
-                    public void onViewAttachedToWindow(View view) {}
-
-                    @Override
-                    public void onViewDetachedFromWindow(View view) {
-                        setImportantForAccessibility(originalImportanceForAccessibility);
-                        widgetsFullSheet.removeOnAttachStateChangeListener(this);
-                    }
-                });
-                return true;
-            } else if (action == SETTINGS) {
-                return OptionsPopupView.startSettings(ScrimView.this);
-            }
-
-            return false;
-        }
-    }
-
-    /**
-     * @return The top of this scrim view, or {@link Float#MAX_VALUE} if there's no distinct top.
-     */
-    public float getVisualTop() {
-        return Float.MAX_VALUE;
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultPeopleView.java b/src/com/android/launcher3/views/SearchResultPeopleView.java
new file mode 100644
index 0000000..6e45e88
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultPeopleView.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static android.content.Intent.URI_ALLOW_UNSAFE;
+import static android.content.Intent.URI_ANDROID_APP_SCHEME;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.util.Themes;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+
+/**
+ * A view representing a single people search result in all apps
+ */
+public class SearchResultPeopleView extends LinearLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+
+    private final int mIconSize;
+    private final int mButtonSize;
+    private final PackageManager mPackageManager;
+    private View mIconView;
+    private TextView mTitleView;
+    private ImageButton[] mProviderButtons = new ImageButton[3];
+    private AllAppsSearchPlugin mPlugin;
+    private Uri mContactUri;
+
+
+    public SearchResultPeopleView(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultPeopleView(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultPeopleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        DeviceProfile deviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
+        mPackageManager = getContext().getPackageManager();
+        mIconSize = deviceProfile.iconSizePx;
+        mButtonSize = (int) (deviceProfile.iconSizePx / 1.5f);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
+        mIconView.getLayoutParams().height = mIconSize;
+        mIconView.getLayoutParams().width = mIconSize;
+        mTitleView = findViewById(R.id.title);
+        mProviderButtons[0] = findViewById(R.id.provider_0);
+        mProviderButtons[1] = findViewById(R.id.provider_1);
+        mProviderButtons[2] = findViewById(R.id.provider_2);
+        for (ImageButton button : mProviderButtons) {
+            button.getLayoutParams().width = mButtonSize;
+            button.getLayoutParams().height = mButtonSize;
+        }
+        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
+    }
+
+    @Override
+    public void applyAdapterInfo(
+            AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle payload = adapterItemWithPayload.getPayload();
+        mPlugin = adapterItemWithPayload.getPlugin();
+        mTitleView.setText(payload.getString("title"));
+        mContactUri = payload.getParcelable("contact_uri");
+        Bitmap icon = payload.getParcelable("icon");
+        if (icon != null) {
+            RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
+            float radius = Themes.getDialogCornerRadius(getContext());
+            d.setCornerRadius(radius);
+            d.setBounds(0, 0, mIconSize, mIconSize);
+            BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
+                    Bitmap.createScaledBitmap(icon, mIconSize, mIconSize, false));
+            mIconView.setBackground(d);
+        }
+
+        ArrayList<Bundle> providers = payload.getParcelableArrayList("providers");
+        for (int i = 0; i < mProviderButtons.length; i++) {
+            ImageButton button = mProviderButtons[i];
+            if (providers != null && i < providers.size()) {
+                try {
+                    Bundle provider = providers.get(i);
+                    Intent intent = Intent.parseUri(provider.getString("intent_uri_str"),
+                            URI_ANDROID_APP_SCHEME | URI_ALLOW_UNSAFE);
+                    setupProviderButton(button, provider, intent);
+                    String pkg = provider.getString("package_name");
+                    UI_HELPER_EXECUTOR.post(() -> {
+                        try {
+                            ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
+                                    pkg, 0);
+                            Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
+                            MAIN_EXECUTOR.post(()-> button.setImageDrawable(appIcon));
+                        } catch (PackageManager.NameNotFoundException ignored) {
+                        }
+
+                    });
+                } catch (URISyntaxException ex) {
+                    button.setVisibility(GONE);
+                }
+            } else {
+                button.setVisibility(GONE);
+            }
+        }
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+    }
+
+    private void setupProviderButton(ImageButton button, Bundle provider, Intent intent) {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        button.setOnClickListener(b -> {
+            launcher.startActivitySafely(b, intent, null);
+            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                    SearchTarget.ItemType.PEOPLE,
+                    SearchTargetEvent.CHILD_SELECT);
+            searchTargetEvent.bundle = new Bundle();
+            searchTargetEvent.bundle.putParcelable("contact_uri", mContactUri);
+            searchTargetEvent.bundle.putBundle("provider", provider);
+            if (mPlugin != null) {
+                mPlugin.notifySearchTargetEvent(searchTargetEvent);
+            }
+        });
+    }
+
+
+    private void handleSelection(int eventType) {
+        if (mContactUri != null) {
+            Launcher launcher = Launcher.getLauncher(getContext());
+            launcher.startActivitySafely(this, new Intent(Intent.ACTION_VIEW, mContactUri).setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK), null);
+            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                    SearchTarget.ItemType.PEOPLE, eventType);
+            searchTargetEvent.bundle = new Bundle();
+            searchTargetEvent.bundle.putParcelable("contact_uri", mContactUri);
+            if (mPlugin != null) {
+                mPlugin.notifySearchTargetEvent(searchTargetEvent);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
new file mode 100644
index 0000000..8624609
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -0,0 +1,171 @@
+/*
+ * 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.views;
+
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.io.IOException;
+import java.net.URL;
+
+/**
+ * A View representing a PlayStore item.
+ */
+public class SearchResultPlayItem extends LinearLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+    private final DeviceProfile mDeviceProfile;
+    private View mIconView;
+    private TextView mTitleView;
+    private TextView[] mDetailViews = new TextView[3];
+    private Button mPreviewButton;
+    private String mPackageName;
+    private boolean mIsInstantGame;
+    private AllAppsSearchPlugin mPlugin;
+
+    public SearchResultPlayItem(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultPlayItem(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultPlayItem(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mDeviceProfile = Launcher.getLauncher(getContext()).getDeviceProfile();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
+        mTitleView = findViewById(R.id.title_view);
+        mPreviewButton = findViewById(R.id.try_button);
+        mPreviewButton.setOnClickListener(view -> launchInstantGame());
+        mDetailViews[0] = findViewById(R.id.detail_0);
+        mDetailViews[1] = findViewById(R.id.detail_1);
+        mDetailViews[2] = findViewById(R.id.detail_2);
+
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = mDeviceProfile.allAppsIconSizePx;
+        iconParams.width = mDeviceProfile.allAppsIconSizePx;
+        setOnClickListener(view -> handleSelection(SearchTargetEvent.SELECT));
+
+    }
+
+    @Override
+    public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle bundle = adapterItemWithPayload.getPayload();
+        mPlugin = adapterItemWithPayload.getPlugin();
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+        if (bundle.getString("package", "").equals(mPackageName)) {
+            return;
+        }
+        mIsInstantGame = bundle.getBoolean("instant_game", false);
+        mPackageName = bundle.getString("package");
+        mPreviewButton.setVisibility(mIsInstantGame ? VISIBLE : GONE);
+        mTitleView.setText(bundle.getString("title"));
+//        TODO: Should use a generic type to get values b/165320033
+        showIfNecessary(mDetailViews[0], bundle.getString("price"));
+        showIfNecessary(mDetailViews[1], bundle.getString("rating"));
+        showIfNecessary(mDetailViews[2], bundle.getString("category"));
+
+        mIconView.setBackgroundResource(R.drawable.ic_deepshortcut_placeholder);
+        UI_HELPER_EXECUTOR.execute(() -> {
+            try {
+//                TODO: Handle caching
+                URL url = new URL(bundle.getString("icon_url"));
+                Bitmap bitmap = BitmapFactory.decodeStream(url.openStream());
+                BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(),
+                        Bitmap.createScaledBitmap(bitmap, mDeviceProfile.allAppsIconSizePx,
+                                mDeviceProfile.allAppsIconSizePx, false));
+                mIconView.post(() -> mIconView.setBackground(bitmapDrawable));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    private void showIfNecessary(TextView textView, @Nullable String string) {
+        if (string == null || string.isEmpty()) {
+            textView.setVisibility(GONE);
+        } else {
+            textView.setText(string);
+            textView.setVisibility(VISIBLE);
+        }
+    }
+
+    private void handleSelection(int eventType) {
+        if (mPackageName == null) return;
+        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
+                "https://play.google.com/store/apps/details?id="
+                        + mPackageName));
+        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(i);
+        logSearchEvent(eventType);
+    }
+
+    private void launchInstantGame() {
+        if (!mIsInstantGame) return;
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        String referrer = "Pixel_Launcher";
+        String id = mPackageName;
+        String deepLinkUrl = "market://details?id=" + id + "&launch=true&referrer=" + referrer;
+        intent.setPackage("com.android.vending");
+        intent.setData(Uri.parse(deepLinkUrl));
+        intent.putExtra("overlay", true);
+        intent.putExtra("callerId", getContext().getPackageName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+        logSearchEvent(SearchTargetEvent.CHILD_SELECT);
+    }
+
+    private void logSearchEvent(int eventType) {
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                SearchTarget.ItemType.PLAY_RESULTS, eventType);
+        searchTargetEvent.bundle = new Bundle();
+        searchTargetEvent.bundle.putString("package_name", mPackageName);
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(searchTargetEvent);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultShortcut.java b/src/com/android/launcher3/views/SearchResultShortcut.java
new file mode 100644
index 0000000..307cf34
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultShortcut.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * A view representing a stand alone shortcut search result
+ */
+public class SearchResultShortcut extends FrameLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
+
+    private BubbleTextView mBubbleTextView;
+    private View mIconView;
+    private ShortcutInfo mShortcutInfo;
+    private AllAppsSearchPlugin mPlugin;
+
+    public SearchResultShortcut(@NonNull Context context) {
+        super(context);
+    }
+
+    public SearchResultShortcut(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchResultShortcut(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Launcher launcher = Launcher.getLauncher(getContext());
+        DeviceProfile grid = launcher.getDeviceProfile();
+        mIconView = findViewById(R.id.icon);
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = grid.allAppsIconSizePx;
+        iconParams.width = grid.allAppsIconSizePx;
+        mBubbleTextView = findViewById(R.id.bubble_text);
+        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
+    }
+
+    @Override
+    public void applyAdapterInfo(
+            AllAppsGridAdapter.AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
+        SearchTarget payload = adapterItemWithPayload.getPayload();
+        mPlugin = adapterItemWithPayload.getPlugin();
+        mShortcutInfo = payload.shortcuts.get(0);
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
+        mBubbleTextView.applyFromWorkspaceItem(workspaceItemInfo);
+        mIconView.setBackground(mBubbleTextView.getIcon());
+        LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
+        MODEL_EXECUTOR.execute(() -> {
+            launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
+            mBubbleTextView.applyFromWorkspaceItem(workspaceItemInfo);
+            mIconView.setBackground(mBubbleTextView.getIcon());
+        });
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+    }
+
+    private void handleSelection(int eventType) {
+        WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) mBubbleTextView.getTag();
+        ItemClickHandler.onClickAppShortcut(this, itemInfo, Launcher.getLauncher(getContext()));
+
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                SearchTarget.ItemType.SHORTCUT, eventType);
+        searchTargetEvent.shortcut = mShortcutInfo;
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(searchTargetEvent);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
new file mode 100644
index 0000000..d439ee3
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+
+/**
+ * Header text view that shows a title for a given section in All apps search
+ */
+public class SearchSectionHeaderView extends TextView implements
+        AllAppsSearchBarController.PayloadResultHandler<String> {
+    public SearchSectionHeaderView(Context context) {
+        super(context);
+    }
+
+    public SearchSectionHeaderView(Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchSectionHeaderView(Context context, @Nullable AttributeSet attrs, int styleAttr) {
+        super(context, attrs, styleAttr);
+    }
+
+    @Override
+    public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
+        String title = adapterItem.getPayload();
+        if (title == null || !title.isEmpty()) {
+            setText(title);
+            setVisibility(VISIBLE);
+        } else {
+            setVisibility(INVISIBLE);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
new file mode 100644
index 0000000..93bcee2
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.util.ArrayList;
+
+/**
+ * A row of tappable TextViews with a breadcrumb for settings search.
+ */
+public class SearchSettingsRowView extends LinearLayout implements
+        View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+
+    private TextView mTitleView;
+    private TextView mDescriptionView;
+    private TextView mBreadcrumbsView;
+    private Intent mIntent;
+    private AllAppsSearchPlugin mPlugin;
+
+    public SearchSettingsRowView(@NonNull Context context) {
+        super(context);
+    }
+
+    public SearchSettingsRowView(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mTitleView = findViewById(R.id.title);
+        mDescriptionView = findViewById(R.id.description);
+        mBreadcrumbsView = findViewById(R.id.breadcrumbs);
+        setOnClickListener(this);
+    }
+
+    @Override
+    public void applyAdapterInfo(
+            AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
+        Bundle bundle = adapterItemWithPayload.getPayload();
+        mPlugin = adapterItemWithPayload.getPlugin();
+        mIntent = bundle.getParcelable("intent");
+        showIfAvailable(mTitleView, bundle.getString("title"));
+        showIfAvailable(mDescriptionView, bundle.getString("description"));
+        ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
+        //TODO: implement RTL friendly breadcrumbs view
+        showIfAvailable(mBreadcrumbsView, breadcrumbs != null
+                ? String.join(" > ", breadcrumbs) : null);
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+    }
+
+    private void showIfAvailable(TextView view, @Nullable String string) {
+        if (TextUtils.isEmpty(string)) {
+            view.setVisibility(GONE);
+        } else {
+            view.setVisibility(VISIBLE);
+            view.setText(string);
+        }
+    }
+
+    @Override
+    public void onClick(View view) {
+        handleSelection(SearchTargetEvent.SELECT);
+    }
+
+    private void handleSelection(int eventType) {
+        if (mIntent == null) return;
+        // TODO: create ItemInfo object and then use it to call startActivityForResult for proper
+        //  WW logging
+        Launcher launcher = Launcher.getLauncher(getContext());
+        launcher.startActivityForResult(mIntent, 0);
+
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                SearchTarget.ItemType.SETTINGS_ROW, eventType);
+        searchTargetEvent.bundle = new Bundle();
+        searchTargetEvent.bundle.putParcelable("intent", mIntent);
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(searchTargetEvent);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/Snackbar.java b/src/com/android/launcher3/views/Snackbar.java
index 513ce59..49fcd2e 100644
--- a/src/com/android/launcher3/views/Snackbar.java
+++ b/src/com/android/launcher3/views/Snackbar.java
@@ -167,11 +167,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // TODO
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_SNACKBAR) != 0;
     }
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index d35a38f..d6737db 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.statemanager.StateManager.StateListener;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 /**
  * On boarding flow for users right after setting up work profile
@@ -89,16 +88,6 @@
     }
 
     @Override
-    public void logActionCommand(int command) {
-        // Since this is on-boarding popup, it is not a user controlled action.
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return LauncherLogProto.ContainerType.TIP;
-    }
-
-    @Override
     protected boolean isOfType(int type) {
         return (type & TYPE_ON_BOARD_POPUP) != 0;
     }
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 3e5113a..01af96c 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.widget;
 
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.content.Context;
 import android.graphics.Point;
@@ -31,20 +30,15 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.AbstractSlideInView;
 
-import java.util.ArrayList;
-
 /**
  * Base class for various widgets popup
  */
@@ -149,28 +143,6 @@
                 isSheetDark ? SystemUiController.FLAG_DARK_NAV : SystemUiController.FLAG_LIGHT_NAV);
     }
 
-    @Override
-    public void fillInLogContainerData(ItemInfo childInfo, Target child,
-            ArrayList<Target> parents) {
-        Target target = newContainerTarget(ContainerType.WIDGETS);
-        target.cardinality = getElementsRowCount();
-        parents.add(target);
-    }
-
-    @Override
-    public final void logActionCommand(int command) {
-        Target target = newContainerTarget(getLogContainerType());
-        target.cardinality = getElementsRowCount();
-        mLauncher.getUserEventDispatcher().logActionCommand(command, target);
-    }
-
-    @Override
-    public int getLogContainerType() {
-        return ContainerType.WIDGETS;
-    }
-
-    protected abstract int getElementsRowCount();
-
     protected SystemUiController getSystemUiController() {
         return mLauncher.getSystemUiController();
     }
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 6f2e179..780a1a1 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -80,9 +80,7 @@
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
 
-        if (Utilities.ATLEAST_OREO) {
-            setExecutor(Executors.THREAD_POOL_EXECUTOR);
-        }
+        setExecutor(Executors.THREAD_POOL_EXECUTOR);
         if (Utilities.ATLEAST_Q && Themes.getAttrBoolean(mLauncher, R.attr.isWorkspaceDarkText)) {
             setOnLightBackground(true);
         }
diff --git a/src/com/android/launcher3/widget/WidgetManagerHelper.java b/src/com/android/launcher3/widget/WidgetManagerHelper.java
index 4b6c569..c0c5c48 100644
--- a/src/com/android/launcher3/widget/WidgetManagerHelper.java
+++ b/src/com/android/launcher3/widget/WidgetManagerHelper.java
@@ -23,13 +23,11 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Process;
 import android.os.UserHandle;
 
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.pm.UserCache;
@@ -84,22 +82,8 @@
             return allWidgetsSteam(mContext).collect(Collectors.toList());
         }
 
-        if (Utilities.ATLEAST_OREO) {
-            return mAppWidgetManager.getInstalledProvidersForPackage(
-                    packageUser.mPackageName, packageUser.mUser);
-        }
-
-        String pkg = packageUser.mPackageName;
-        return Stream.concat(
-                // Only get providers for the given package/user.
-                mAppWidgetManager.getInstalledProvidersForProfile(packageUser.mUser)
-                        .stream()
-                        .filter(w -> w.provider.equals(pkg)),
-                Process.myUserHandle().equals(packageUser.mUser)
-                        && mContext.getPackageName().equals(pkg)
-                        ? CustomWidgetManager.INSTANCE.get(mContext).stream()
-                        : Stream.empty())
-                .collect(Collectors.toList());
+        return mAppWidgetManager.getInstalledProvidersForPackage(
+                packageUser.mPackageName, packageUser.mUser);
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index 30be7a6..3585a18 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -180,11 +180,6 @@
     }
 
     @Override
-    protected int getElementsRowCount() {
-        return 1;
-    }
-
-    @Override
     protected Pair<View, String> getAccessibilityTarget() {
         return Pair.create(findViewById(R.id.title),  getContext().getString(
                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 68a3ec5..4c8c339 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -243,11 +243,6 @@
     }
 
     @Override
-    protected int getElementsRowCount() {
-        return mAdapter.getItemCount();
-    }
-
-    @Override
     public void addHintCloseAnim(
             float distanceToMove, Interpolator interpolator, PendingAnimation target) {
         target.setFloat(mRecyclerView, VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
diff --git a/src/com/android/launcher3/widget/WidgetsListAdapter.java b/src/com/android/launcher3/widget/WidgetsListAdapter.java
index a45521d..5bf9690 100644
--- a/src/com/android/launcher3/widget/WidgetsListAdapter.java
+++ b/src/com/android/launcher3/widget/WidgetsListAdapter.java
@@ -23,9 +23,12 @@
 import android.view.View.OnLongClickListener;
 import android.view.ViewGroup;
 
-import com.android.launcher3.icons.IconCache;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.recyclerview.widget.RecyclerView.Adapter;
+
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.LabelComparator;
 
@@ -34,9 +37,6 @@
 import java.util.Comparator;
 import java.util.List;
 
-import androidx.recyclerview.widget.RecyclerView;
-import androidx.recyclerview.widget.RecyclerView.Adapter;
-
 /**
  * List view adapter for the widget tray.
  *
@@ -128,7 +128,7 @@
         int childCount = row.getChildCount();
 
         if (expectedChildCount > childCount) {
-            for (int i = childCount ; i < expectedChildCount; i++) {
+            for (int i = childCount; i < expectedChildCount; i++) {
                 if ((i & 1) == 1) {
                     // Add a divider for odd index
                     mLayoutInflater.inflate(R.layout.widget_list_divider, row);
@@ -144,24 +144,24 @@
                 }
             }
         } else if (expectedChildCount < childCount) {
-            for (int i = expectedChildCount ; i < childCount; i++) {
+            for (int i = expectedChildCount; i < childCount; i++) {
                 row.getChildAt(i).setVisibility(View.GONE);
             }
         }
 
         // Bind the views in the application info section.
-        holder.title.applyFromPackageItemInfo(entry.pkgItem);
+        holder.title.applyFromItemInfoWithIcon(entry.pkgItem);
 
         // Bind the view in the widget horizontal tray region.
-        for (int i=0; i < infoList.size(); i++) {
-            WidgetCell widget = (WidgetCell) row.getChildAt(2*i);
+        for (int i = 0; i < infoList.size(); i++) {
+            WidgetCell widget = (WidgetCell) row.getChildAt(2 * i);
             widget.applyFromCellItem(infoList.get(i), mWidgetPreviewLoader);
             widget.setApplyBitmapDeferred(mApplyBitmapDeferred);
             widget.ensurePreview();
             widget.setVisibility(View.VISIBLE);
 
             if (i > 0) {
-                row.getChildAt(2*i - 1).setVisibility(View.VISIBLE);
+                row.getChildAt(2 * i - 1).setVisibility(View.VISIBLE);
             }
         }
     }
@@ -185,7 +185,7 @@
     @Override
     public void onViewRecycled(WidgetsRowViewHolder holder) {
         int total = holder.cellContainer.getChildCount();
-        for (int i = 0; i < total; i+=2) {
+        for (int i = 0; i < total; i += 2) {
             WidgetCell widget = (WidgetCell) holder.cellContainer.getChildAt(i);
             widget.clear();
         }
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
index c57f07d..4fa670f 100644
--- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -17,34 +17,45 @@
 package com.android.systemui.plugins;
 
 import android.app.Activity;
-import android.view.ViewGroup;
-import android.widget.EditText;
+import android.view.View;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.util.List;
+import java.util.function.Consumer;
 
 /**
- * Implement this plugin interface to replace the all apps recycler view of the all apps drawer.
+ * Implement this plugin interface to fetch search result data from the plugin side.
  */
 @ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
 public interface AllAppsSearchPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
-    int VERSION = 3;
+    int VERSION = 6;
 
-    /** Following are the order that these methods should be called. */
-    void setup(ViewGroup parent, Activity activity, float allAppsContainerHeight);
+    void setup(Activity activity, View view);
 
     /**
-     * When drag starts, pass window inset related fields and the progress to indicate
-     * whether user is swiping down or swiping up
+     * Send launcher state related signals.
      */
-    void onDragStart(float progress);
+    void onStateTransitionStart(int fromState, int toState);
+    void onStateTransitionComplete(int state);
 
-    /** progress is between [0, 1] 1: down, 0: up */
-    void setProgress(float progress);
+    /**
+     * Send signal when user starts typing, perform search, when search ends
+     */
+    void startedSearchSession();
+    void performSearch(String query, Consumer<List<SearchTarget>> results);
 
-    /** Called when container animation stops, so that plugin can perform cleanups */
-    void onAnimationEnd(float progress);
+    /**
+     * Send over search target interaction events to Plugin
+     */
+    void notifySearchTargetEvent(SearchTargetEvent event);
 
-    /** pass over the search box object */
-    void setEditText(EditText editText);
-}
+    /**
+     * Launcher activity lifecycle callbacks
+     */
+    void onResume(int state);
+    void onStop(int state);
+}
\ No newline at end of file
diff --git a/src_plugins/com/android/systemui/plugins/OWNERS b/src_plugins/com/android/systemui/plugins/OWNERS
deleted file mode 100644
index 0514999..0000000
--- a/src_plugins/com/android/systemui/plugins/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# When changing interface for this plugin OR when increasing version code, please add Alex
-# Only add other owners if Alex is not available
-per-file AllAppsSearchPlugin.java, globs = set noparent
-per-file AllAppsSearchPlugin.java = alexmang@google.com, hyunyoungs@google.com, sunnygoyal@google.com, twickham@google.com
diff --git a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java b/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
deleted file mode 100644
index 0e3664a..0000000
--- a/src_plugins/com/android/systemui/plugins/UserEventPlugin.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.plugins;
-
-import com.android.systemui.plugins.annotations.ProvidesInterface;
-
-/**
- * Implement this plugin interface to access user event log on the device for prototype purpose.
- * NOTE: plugin is for internal prototype only and is not visible in production environment.
- */
-@ProvidesInterface(action = UserEventPlugin.ACTION, version = UserEventPlugin.VERSION)
-public interface UserEventPlugin extends Plugin {
-    String ACTION = "com.android.launcher3.action.PLUGIN_USER_EVENT_LOG";
-    int VERSION = 1;
-
-    /**
-     * Callback to be triggered whenever an user event occurs.
-     */
-    void onUserEvent(Object event);
-}
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
new file mode 100644
index 0000000..c6b8300
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
@@ -0,0 +1,103 @@
+/*
+ * 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.systemui.plugins.shared;
+
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+
+import java.util.List;
+
+/**
+ * Used to return all apps search targets.
+ */
+public class SearchTarget implements Comparable<SearchTarget> {
+
+    public enum ViewType {
+        TOP_HIT(0),
+        HERO(1),
+        DETAIL(2),
+        ROW(3),
+        ROW_WITH_BUTTON(4),
+        SLICE(5),
+        SHORTCUT(6),
+        PEOPLE(7);
+
+        private final int mId;
+        ViewType(int id) {
+            mId = id;
+        }
+
+        public int get() {
+            return mId;
+        }
+    }
+
+    public enum ItemType {
+        PLAY_RESULTS(0, "Play Store", ViewType.DETAIL),
+        SETTINGS_ROW(1, "Settings", ViewType.ROW),
+        SETTINGS_SLICE(2, "Settings", ViewType.SLICE),
+        APP(3, "", ViewType.TOP_HIT),
+        APP_HERO(4, "", ViewType.HERO),
+        SHORTCUT(5, "Shortcuts", ViewType.SHORTCUT),
+        PEOPLE(6, "People", ViewType.PEOPLE);
+
+        private final int mId;
+        private final String mTitle;
+        private final ViewType mViewType;
+
+        ItemType(int id, String title, ViewType type) {
+            mId = id;
+            mTitle = title;
+            mViewType = type;
+        }
+
+        public ViewType getViewType() {
+            return mViewType;
+        }
+
+        public String getTitle() {
+            return mTitle;
+        }
+
+        public int getId() {
+            return mId;
+        }
+    }
+
+    public ItemType type;
+    public List<ShortcutInfo> shortcuts;
+    public Bundle bundle;
+    public float score;
+
+    /**
+     * Constructor to create the search target. Bundle is currently temporary to hold
+     * search target primitives that cannot be expressed as java primitive objects
+     * or AOSP native objects.
+     *
+     */
+    public SearchTarget(ItemType itemType, List<ShortcutInfo> shortcuts,
+            Bundle bundle, float score) {
+        this.type = itemType;
+        this.shortcuts = shortcuts;
+        this.bundle = bundle;
+        this.score = score;
+    }
+
+    @Override
+    public int compareTo(SearchTarget o) {
+        return Float.compare(o.score, score);
+    }
+}
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
new file mode 100644
index 0000000..ac4bc33
--- /dev/null
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.plugins.shared;
+
+import android.content.pm.ShortcutInfo;
+import android.os.Bundle;
+
+/**
+ * Event used for the feedback loop to the plugin. (and future aiai)
+ */
+public class SearchTargetEvent {
+    public static final int SELECT = 0;
+    public static final int QUICK_SELECT = 1;
+    public static final int LONG_PRESS = 2;
+    public static final int CHILD_SELECT = 3;
+
+    public SearchTarget.ItemType type;
+    public ShortcutInfo shortcut;
+    public int eventType;
+    public Bundle bundle;
+    public float score;
+
+    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType) {
+        this.type = itemType;
+        this.eventType = eventType;
+    }
+}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
index dcb4636..269af7b 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
 import java.util.ArrayList;
@@ -34,12 +33,7 @@
 
     public LoaderResults(LauncherAppState app, BgDataModel dataModel,
             AllAppsList allAppsList, Callbacks[] callbacks) {
-        this(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
-    }
-
-    public LoaderResults(LauncherAppState app, BgDataModel dataModel,
-            AllAppsList allAppsList, Callbacks[] callbacks, LooperExecutor executor) {
-        super(app, dataModel, allAppsList, callbacks, executor);
+        super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
     }
 
     @Override
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 9d87788..b4e45f8 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
-import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.widget.WidgetItemComparator;
@@ -36,11 +35,12 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Widgets data model that is used by the adapters of the widget views and controllers.
@@ -51,14 +51,13 @@
 
     // True is the widget support is disabled.
     public static final boolean GO_DISABLE_WIDGETS = false;
+    public static final boolean GO_DISABLE_NOTIFICATION_DOTS = false;
 
     private static final String TAG = "WidgetsModel";
     private static final boolean DEBUG = false;
 
     /* Map of widgets and shortcuts that are tracked per package. */
-    private final MultiHashMap<PackageItemInfo, WidgetItem> mWidgetsList = new MultiHashMap<>();
-
-    private AppFilter mAppFilter;
+    private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsList = new HashMap<>();
 
     /**
      * Returns a list of {@link WidgetListRowEntry}. All {@link WidgetItem} in a single row
@@ -73,8 +72,9 @@
         AlphabeticIndexCompat indexer = new AlphabeticIndexCompat(context);
 
         WidgetItemComparator widgetComparator = new WidgetItemComparator();
-        for (Map.Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
-            WidgetListRowEntry row = new WidgetListRowEntry(entry.getKey(), entry.getValue());
+        for (Map.Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
+            WidgetListRowEntry row = new WidgetListRowEntry(
+                    entry.getKey(), new ArrayList<>(entry.getValue()));
             row.titleSectionName = (row.pkgItem.title == null) ? "" :
                     indexer.computeSectionName(row.pkgItem.title);
             Collections.sort(row.widgets, widgetComparator);
@@ -145,77 +145,42 @@
         if (packageUser == null) {
             mWidgetsList.clear();
         } else {
-            // Only clear the widgets for the given package/user.
-            PackageItemInfo packageItem = null;
-            for (PackageItemInfo item : mWidgetsList.keySet()) {
-                if (item.packageName.equals(packageUser.mPackageName)) {
-                    packageItem = item;
-                    break;
-                }
-            }
+            PackageItemInfo packageItem = mWidgetsList.keySet()
+                    .stream()
+                    .filter(item -> item.packageName.equals(packageUser.mPackageName))
+                    .findFirst()
+                    .orElse(null);
             if (packageItem != null) {
                 // We want to preserve the user that was on the packageItem previously,
                 // so add it to tmpPackageItemInfos here to avoid creating a new entry.
                 tmpPackageItemInfos.put(packageItem.packageName, packageItem);
 
-                Iterator<WidgetItem> widgetItemIterator = mWidgetsList.get(packageItem).iterator();
-                while (widgetItemIterator.hasNext()) {
-                    WidgetItem nextWidget = widgetItemIterator.next();
-                    if (nextWidget.componentName.getPackageName().equals(packageUser.mPackageName)
-                            && nextWidget.user.equals(packageUser.mUser)) {
-                        widgetItemIterator.remove();
-                    }
-                }
+                // Add the widgets for other users in the rawList as it only contains widgets for
+                // packageUser
+                List<WidgetItem> otherUserItems = mWidgetsList.remove(packageItem);
+                otherUserItems.removeIf(w -> w.user.equals(packageUser.mUser));
+                rawWidgetsShortcuts.addAll(otherUserItems);
             }
         }
 
-        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
         UserHandle myUser = Process.myUserHandle();
 
         // add and update.
-        for (WidgetItem item : rawWidgetsShortcuts) {
-            if (item.widgetInfo != null) {
-                if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
-                    // Widget is hidden from picker
-                    continue;
-                }
-
-                // Ensure that all widgets we show can be added on a workspace of this size
-                int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
-                int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
-                if (minSpanX > idp.numColumns || minSpanY > idp.numRows) {
-                    if (DEBUG) {
-                        Log.d(TAG, String.format(
-                                "Widget %s : (%d X %d) can't fit on this device",
-                                item.componentName, minSpanX, minSpanY));
+        mWidgetsList.putAll(rawWidgetsShortcuts.stream()
+                .filter(new WidgetValidityCheck(app))
+                .collect(Collectors.groupingBy(item -> {
+                    String packageName = item.componentName.getPackageName();
+                    PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
+                    if (pInfo == null) {
+                        pInfo = new PackageItemInfo(packageName);
+                        pInfo.user = item.user;
+                        tmpPackageItemInfos.put(packageName,  pInfo);
+                    } else if (!myUser.equals(pInfo.user)) {
+                        // Keep updating the user, until we get the primary user.
+                        pInfo.user = item.user;
                     }
-                    continue;
-                }
-            }
-
-            if (mAppFilter == null) {
-                mAppFilter = AppFilter.newInstance(app.getContext());
-            }
-            if (!mAppFilter.shouldShowApp(item.componentName)) {
-                if (DEBUG) {
-                    Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
-                            item.componentName));
-                }
-                continue;
-            }
-
-            String packageName = item.componentName.getPackageName();
-            PackageItemInfo pInfo = tmpPackageItemInfos.get(packageName);
-            if (pInfo == null) {
-                pInfo = new PackageItemInfo(packageName);
-                pInfo.user = item.user;
-                tmpPackageItemInfos.put(packageName,  pInfo);
-            } else if (!myUser.equals(pInfo.user)) {
-                // Keep updating the user, until we get the primary user.
-                pInfo.user = item.user;
-            }
-            mWidgetsList.addToList(pInfo, item);
-        }
+                    return pInfo;
+                })));
 
         // Update each package entry
         IconCache iconCache = app.getIconCache();
@@ -226,9 +191,9 @@
 
     public void onPackageIconsUpdated(Set<String> packageNames, UserHandle user,
             LauncherAppState app) {
-        for (Entry<PackageItemInfo, ArrayList<WidgetItem>> entry : mWidgetsList.entrySet()) {
+        for (Entry<PackageItemInfo, List<WidgetItem>> entry : mWidgetsList.entrySet()) {
             if (packageNames.contains(entry.getKey().packageName)) {
-                ArrayList<WidgetItem> items = entry.getValue();
+                List<WidgetItem> items = entry.getValue();
                 int count = items.size();
                 for (int i = 0; i < count; i++) {
                     WidgetItem item = items.get(i);
@@ -248,7 +213,7 @@
 
     public WidgetItem getWidgetProviderInfoByProviderName(
             ComponentName providerName) {
-        ArrayList<WidgetItem> widgetsList = mWidgetsList.get(
+        List<WidgetItem> widgetsList = mWidgetsList.get(
                 new PackageItemInfo(providerName.getPackageName()));
         if (widgetsList == null) {
             return null;
@@ -261,4 +226,46 @@
         }
         return null;
     }
+
+    private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+
+        private final InvariantDeviceProfile mIdp;
+        private final AppFilter mAppFilter;
+
+        WidgetValidityCheck(LauncherAppState app) {
+            mIdp = app.getInvariantDeviceProfile();
+            mAppFilter = new AppFilter(app.getContext());
+        }
+
+        @Override
+        public boolean test(WidgetItem item) {
+            if (item.widgetInfo != null) {
+                if ((item.widgetInfo.getWidgetFeatures() & WIDGET_FEATURE_HIDE_FROM_PICKER) != 0) {
+                    // Widget is hidden from picker
+                    return false;
+                }
+
+                // Ensure that all widgets we show can be added on a workspace of this size
+                int minSpanX = Math.min(item.widgetInfo.spanX, item.widgetInfo.minSpanX);
+                int minSpanY = Math.min(item.widgetInfo.spanY, item.widgetInfo.minSpanY);
+                if (minSpanX > mIdp.numColumns || minSpanY > mIdp.numRows) {
+                    if (DEBUG) {
+                        Log.d(TAG, String.format(
+                                "Widget %s : (%d X %d) can't fit on this device",
+                                item.componentName, minSpanX, minSpanY));
+                    }
+                    return false;
+                }
+            }
+            if (!mAppFilter.shouldShowApp(item.componentName)) {
+                if (DEBUG) {
+                    Log.d(TAG, String.format("%s is filtered and not added to the widget tray.",
+                            item.componentName));
+                }
+                return false;
+            }
+
+            return true;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
index 5a1f9ca..780a0f0 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/ColorExtractionAlgorithm.java
@@ -21,16 +21,16 @@
 import android.util.Pair;
 import android.util.Range;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.ColorUtils;
+
 import com.android.launcher3.Utilities;
 
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.graphics.ColorUtils;
-
 /**
  * Implementation of tonal color extraction
  **/
@@ -69,7 +69,7 @@
         // palettes. The best fit is tweaked to be closer to the source color
         // and replaces the original palette
 
-        // Get the most preeminent, non-blacklisted color.
+        // Get the most preeminent, non-disallowed color.
         Integer bestColor = 0;
         final float[] hsl = new float[3];
         for (int i = 0; i < mainColorsSize; i++) {
@@ -78,7 +78,7 @@
                     Color.blue(colorValue), hsl);
 
             // Stop when we find a color that meets our criteria
-            if (!isBlacklisted(hsl)) {
+            if (!isDisallowed(hsl)) {
                 bestColor = colorValue;
                 break;
             }
@@ -167,12 +167,12 @@
     }
 
     /**
-     * Checks if a given color exists in the blacklist
+     * Checks if a given color exists in the disallowed_colors list.
      * @param hsl float array with 3 components (H 0..360, S 0..1 and L 0..1)
      * @return true if color should be avoided
      */
-    private boolean isBlacklisted(float[] hsl) {
-        for (ColorRange badRange: BLACKLISTED_COLORS) {
+    private boolean isDisallowed(float[] hsl) {
+        for (ColorRange badRange: DISALLOWED_COLORS) {
             if (badRange.containsColor(hsl[0], hsl[1], hsl[2])) {
                 return true;
             }
@@ -592,7 +592,7 @@
     );
 
     @SuppressWarnings("WeakerAccess")
-    static final ColorRange[] BLACKLISTED_COLORS = new ColorRange[] {
+    static final ColorRange[] DISALLOWED_COLORS = new ColorRange[] {
 
             // Red
             new ColorRange(
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
index 0fd0a35..9dbe47c 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompat.java
@@ -17,8 +17,7 @@
 package com.android.launcher3.uioverrides.dynamicui;
 
 import android.content.Context;
-
-import com.android.launcher3.Utilities;
+import android.os.Build;
 
 import androidx.annotation.Nullable;
 
@@ -32,7 +31,7 @@
             if (sInstance == null) {
                 context = context.getApplicationContext();
 
-                if (Utilities.ATLEAST_OREO_MR1) {
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
                     try {
                         sInstance = new WallpaperManagerCompatVOMR1(context);
                     } catch (Throwable e) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index 7a6332c..d102bcc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -38,10 +38,6 @@
         return new OverviewState(id);
     }
 
-    public static OverviewState newPeekState(int id) {
-        return new OverviewState(id);
-    }
-
     public static OverviewState newSwitchState(int id) {
         return new OverviewState(id);
     }
diff --git a/tests/src/com/android/launcher3/testcomponent/ListViewService.java b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
index 3da20e0..9e3a492 100644
--- a/tests/src/com/android/launcher3/testcomponent/ListViewService.java
+++ b/tests/src/com/android/launcher3/testcomponent/ListViewService.java
@@ -89,7 +89,7 @@
                 public RemoteViewsFactory onGetViewFactory(Intent intent) {
                     return SimpleViewsFactory.this;
                 }
-            }.onBind(new Intent("dummy_intent"));
+            }.onBind(new Intent("stub_intent"));
         }
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 858e183..5e42d9b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -53,7 +53,6 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.common.WidgetUtils;
-import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
@@ -116,8 +115,7 @@
         if (TestHelpers.isInLauncherProcess()) {
             StrictMode.VmPolicy.Builder builder =
                     new StrictMode.VmPolicy.Builder()
-// b/154772063
-//                            .detectActivityLeaks()
+                            .detectActivityLeaks()
                             .penaltyLog()
                             .penaltyListener(Runnable::run, violation -> {
                                 if (sStrictmodeDetectedActivityLeak == null) {
@@ -272,8 +270,6 @@
         }
 
         mLauncherPid = 0;
-        // Disable app tracker
-        AppLaunchTracker.INSTANCE.initializeForTesting(new AppLaunchTracker());
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
         mTargetPackage = mTargetContext.getPackageName();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 8d571ff..f5f93c4 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -24,6 +24,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.Log;
 import android.widget.TextView;
 
 import androidx.test.filters.LargeTest;
@@ -34,6 +35,7 @@
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.allapps.WorkModeSwitch;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.WorkEduView;
 
 import org.junit.After;
@@ -132,6 +134,10 @@
                     l.getResources().getString(R.string.work_profile_edu_personal_apps));
             workEduView.findViewById(R.id.proceed).callOnClick();
         });
+
+        executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
+                "Work profile status: " + launcher.getAppsView().isPersonalTabVisible()));
+
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l ->
                 ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage() == WORK_PAGE
diff --git a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index a4aa9f2..fa495f5 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -192,7 +192,7 @@
                         WidgetManagerHelper.WIDGET_OPTION_RESTORE_COMPLETED));
         executeOnLauncher(l -> l.getAppWidgetHost().startListening());
         verifyWidgetPresent(info);
-        assertNull(mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT));
+        assertNull(mLauncher.getWorkspace().tryGetPendingWidget(100));
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 0e43d81..822fefc 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -30,7 +30,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -126,9 +125,6 @@
 
     private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
             Intent... commandIntents) throws Throwable {
-        if (!Utilities.ATLEAST_OREO) {
-            return;
-        }
         clearHomescreen();
         mDevice.pressHome();
 
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 0c4e5a9..c9c846f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -16,8 +16,9 @@
 
 package com.android.launcher3.tapl;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
+
 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
-import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 
 import android.graphics.Point;
@@ -93,7 +94,7 @@
 
                 mLauncher.sendPointer(
                         downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
-                mLauncher.executeAndWaitForEvent(
+                mLauncher.executeAndWaitForLauncherEvent(
                         () -> mLauncher.movePointer(
                                 downTime,
                                 downTime,
@@ -150,67 +151,68 @@
     /**
      * Swipes right or double presses the square button to switch to the previous app.
      */
+    @NonNull
     public Background quickSwitchToPreviousApp() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "want to quick switch to the previous app")) {
             verifyActiveContainer();
-            quickSwitchToPreviousApp(getExpectedStateForQuickSwitch());
+            final boolean launcherWasVisible = mLauncher.isLauncherVisible();
+            boolean transposeInLandscape = false;
+            switch (mLauncher.getNavigationModel()) {
+                case TWO_BUTTON:
+                    transposeInLandscape = true;
+                    // Fall through, zero button and two button modes behave the same.
+                case ZERO_BUTTON: {
+                    final int startX;
+                    final int startY;
+                    final int endX;
+                    final int endY;
+                    if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
+                        // Swipe from the bottom left to the bottom right of the screen.
+                        startX = 0;
+                        startY = getSwipeStartY();
+                        endX = mLauncher.getDevice().getDisplayWidth();
+                        endY = startY;
+                    } else {
+                        // Swipe from the bottom right to the top right of the screen.
+                        startX = getSwipeStartX();
+                        startY = mLauncher.getRealDisplaySize().y - 1;
+                        endX = startX;
+                        endY = 0;
+                    }
+                    final boolean isZeroButton = mLauncher.getNavigationModel()
+                            == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
+                    LauncherInstrumentation.GestureScope gestureScope =
+                            launcherWasVisible && isZeroButton
+                                    ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+                                    : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+                    mLauncher.executeAndWaitForEvent(
+                            () -> mLauncher.linearGesture(
+                                    startX, startY, endX, endY, 20, false, gestureScope),
+                            event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                            () -> "Quick switch gesture didn't change window state");
+                    break;
+                }
+
+                case THREE_BUTTON:
+                    // Double press the recents button.
+                    UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                    mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
+                    mLauncher.getOverview();
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
+                    mLauncher.executeAndWaitForEvent(
+                            () -> recentsButton.click(),
+                            event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                            () -> "Pressing recents button didn't change window state");
+                    break;
+            }
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
             return new Background(mLauncher);
         }
     }
 
-    protected int getExpectedStateForQuickSwitch() {
-        return BACKGROUND_APP_STATE_ORDINAL;
-    }
-
-    protected void quickSwitchToPreviousApp(int expectedState) {
-        final boolean launcherWasVisible = mLauncher.isLauncherVisible();
-        boolean transposeInLandscape = false;
-        switch (mLauncher.getNavigationModel()) {
-            case TWO_BUTTON:
-                transposeInLandscape = true;
-                // Fall through, zero button and two button modes behave the same.
-            case ZERO_BUTTON: {
-                final int startX;
-                final int startY;
-                final int endX;
-                final int endY;
-                if (mLauncher.getDevice().isNaturalOrientation() || !transposeInLandscape) {
-                    // Swipe from the bottom left to the bottom right of the screen.
-                    startX = 0;
-                    startY = getSwipeStartY();
-                    endX = mLauncher.getDevice().getDisplayWidth();
-                    endY = startY;
-                } else {
-                    // Swipe from the bottom right to the top right of the screen.
-                    startX = getSwipeStartX();
-                    startY = mLauncher.getRealDisplaySize().y - 1;
-                    endX = startX;
-                    endY = 0;
-                }
-                final boolean isZeroButton = mLauncher.getNavigationModel()
-                        == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
-                mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
-                        launcherWasVisible && isZeroButton
-                                ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER);
-                break;
-            }
-
-            case THREE_BUTTON:
-                // Double press the recents button.
-                UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
-                mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
-                mLauncher.getOverview();
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
-                recentsButton.click();
-                break;
-        }
-        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
-    }
-
     protected String getSwipeHeightRequestName() {
         return TestProtocol.REQUEST_BACKGROUND_TO_OVERVIEW_SWIPE_HEIGHT;
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index 223ae29..588b6b8 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -27,7 +27,7 @@
 import java.util.List;
 
 /**
- * Common overview pane for both Launcher and fallback recents
+ * Common overview panel for both Launcher and fallback recents
  */
 public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
     private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
@@ -135,4 +135,19 @@
     public boolean hasTasks() {
         return getTasks().size() > 0;
     }
+
+    /**
+     * Gets Overview Actions.
+     *
+     * @return The Overview Actions
+     */
+    @NonNull
+    public OverviewActions getOverviewActions() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get overview actions")) {
+            verifyActiveContainer();
+            UiObject2 overviewActions = mLauncher.waitForLauncherObject("action_buttons");
+            return new OverviewActions(overviewActions, mLauncher);
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index c06e254..0060844 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.tapl;
 
-import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
-
 import androidx.annotation.NonNull;
 
 /**
@@ -65,8 +63,4 @@
         return true;
     }
 
-    @Override
-    protected int getExpectedStateForQuickSwitch() {
-        return QUICK_SWITCH_STATE_ORDINAL;
-    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 13ecfb8..093c024 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -56,15 +56,18 @@
     private Background launch(BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
                 mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+        final String label = mObject.getText();
 
         mLauncher.executeAndWaitForEvent(
-                () -> mLauncher.clickLauncherObject(mObject),
+                () -> {
+                    mLauncher.clickLauncherObject(mObject);
+                    expectActivityStartEvents();
+                },
                 event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                () -> "Launching an app didn't open a new window: " + mObject.getText());
-        expectActivityStartEvents();
+                () -> "Launching an app didn't open a new window: " + label);
 
         mLauncher.assertTrue(
-                "App didn't start: " + selector,
+                "App didn't start: " + label,
                 mLauncher.getDevice().wait(Until.hasObject(selector),
                         LauncherInstrumentation.WAIT_TIME_MS));
         return new Background(mLauncher);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index c74dea9..22833ec 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -64,6 +64,7 @@
 
 import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.systemui.shared.system.ContextUtils;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import org.junit.Assert;
@@ -103,6 +104,7 @@
 
     static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
     static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
+    private final String mLauncherPackage;
 
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
@@ -155,6 +157,7 @@
     public static final int WAIT_TIME_MS = 10000;
     public static final int LONG_WAIT_TIME_MS = 60000;
     private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
+    private static final String ANDROID_PACKAGE = "android";
 
     private static WeakReference<VisibleContainer> sActiveContainer = new WeakReference<>(null);
 
@@ -173,8 +176,7 @@
     private Runnable mOnLauncherCrashed;
 
     private static Pattern getTouchEventPattern(String prefix, String action) {
-        // The pattern includes sanity checks that we don't get a multi-touch events or other
-        // surprises.
+        // The pattern includes checks that we don't get a multi-touch events or other surprises.
         return Pattern.compile(
                 prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
                         + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
@@ -216,11 +218,11 @@
         // Launcher package. As during inproc tests the tested launcher may not be selected as the
         // current launcher, choosing target package for inproc. For out-of-proc, use the installed
         // launcher package.
-        final String authorityPackage = testPackage.equals(targetPackage) ?
-                getLauncherPackageName() :
-                targetPackage;
+        mLauncherPackage = testPackage.equals(targetPackage)
+                ? getLauncherPackageName()
+                : targetPackage;
 
-        String testProviderAuthority = authorityPackage + ".TestInfo";
+        String testProviderAuthority = mLauncherPackage + ".TestInfo";
         mTestProviderUri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(testProviderAuthority)
@@ -237,11 +239,12 @@
 
         if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
             if (TestHelpers.isInLauncherProcess()) {
-                getContext().getPackageManager().setComponentEnabledSetting(
-                        cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+                pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
             } else {
                 try {
-                    mDevice.executeShellCommand("pm enable " + cn.flattenToString());
+                    final int userId = ContextUtils.getUserId(getContext());
+                    mDevice.executeShellCommand(
+                            "pm enable --user " + userId + " " + cn.flattenToString());
                 } catch (IOException e) {
                     fail(e.toString());
                 }
@@ -450,7 +453,7 @@
             }
         }
 
-        dumpDiagnostics();
+        dumpDiagnostics(message);
 
         log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
@@ -458,10 +461,11 @@
         return message;
     }
 
-    private void dumpDiagnostics() {
-        Log.e("b/156287114", "Input:");
+    private void dumpDiagnostics(String message) {
+        log("Diagnostics for failure: " + message);
+        log("Input:");
         logShellCommand("dumpsys input");
-        Log.e("b/156287114", "TIS:");
+        log("TIS:");
         logShellCommand("dumpsys activity service TouchInteractionService");
     }
 
@@ -469,10 +473,10 @@
         try {
             for (String line : mDevice.executeShellCommand(command).split("\\n")) {
                 SystemClock.sleep(10);
-                Log.d("b/156287114", line);
+                log(line);
             }
         } catch (IOException e) {
-            Log.d("b/156287114", "Failed to execute " + command);
+            log("Failed to execute " + command);
         }
     }
 
@@ -628,6 +632,14 @@
         fail("Launcher didn't initialize");
     }
 
+    Parcelable executeAndWaitForLauncherEvent(Runnable command,
+            UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
+        return executeAndWaitForEvent(
+                command,
+                e -> mLauncherPackage.equals(e.getPackageName()) && eventFilter.accept(e),
+                message);
+    }
+
     Parcelable executeAndWaitForEvent(Runnable command,
             UiAutomation.AccessibilityEventFilter eventFilter, Supplier<String> message) {
         try {
@@ -921,6 +933,14 @@
         return waitForObjectBySelector(getOverviewObjectSelector(resName));
     }
 
+    @NonNull
+    UiObject2 waitForAndroidObject(String resId) {
+        final UiObject2 object = mDevice.wait(
+                Until.findObject(By.res(ANDROID_PACKAGE, resId)), WAIT_TIME_MS);
+        assertNotNull("Can't find a android object with id: " + resId, object);
+        return object;
+    }
+
     private UiObject2 waitForObjectBySelector(BySelector selector) {
         final UiObject2 object = mDevice.wait(Until.findObject(selector), WAIT_TIME_MS);
         assertNotNull("Can't find a view in Launcher, selector: " + selector, object);
@@ -972,7 +992,7 @@
 
     void runToState(Runnable command, int expectedState) {
         final List<Integer> actualEvents = new ArrayList<>();
-        executeAndWaitForEvent(
+        executeAndWaitForLauncherEvent(
                 command,
                 event -> isSwitchToStateEvent(event, expectedState, actualEvents),
                 () -> "Failed to receive an event for the state change: expected ["
@@ -1087,7 +1107,7 @@
                 return;
         }
 
-        executeAndWaitForEvent(
+        executeAndWaitForLauncherEvent(
                 () -> linearGesture(
                         startX, startY, endX, endY, steps, slowDown, GestureScope.INSIDE),
                 event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
@@ -1284,16 +1304,17 @@
         if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
             return true;
         }
-        // Overview actions hide all apps
-        if (overviewActionsEnabled()) {
-            return false;
-        }
-        // ...otherwise there should be all apps
-        return true;
+        // ...otherwise there are overview actions, which hide all apps
+        return false;
     }
 
-    private boolean overviewActionsEnabled() {
-        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_ACTIONS_ENABLED).getBoolean(
+    boolean overviewShareEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_SHARE_ENABLED).getBoolean(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
+    boolean overviewContentPushEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_OVERVIEW_CONTENT_PUSH_ENABLED).getBoolean(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
@@ -1362,7 +1383,7 @@
                 if (mCheckEventsForSuccessfulGestures) {
                     final String message = eventChecker.verify(WAIT_TIME_MS, true);
                     if (message != null) {
-                        dumpDiagnostics();
+                        dumpDiagnostics(message);
                         checkForAnomaly();
                         Assert.fail(formatSystemHealthMessage(
                                 "http://go/tapl : successful gesture produced " + message));
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
new file mode 100644
index 0000000..e3e0f42
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -0,0 +1,151 @@
+/*
+ * 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.TestProtocol;
+
+/**
+ * View containing overview actions
+ */
+public class OverviewActions {
+    private final UiObject2 mOverviewActions;
+    private final LauncherInstrumentation mLauncher;
+
+    OverviewActions(UiObject2 overviewActions, LauncherInstrumentation launcherInstrumentation) {
+        this.mOverviewActions = overviewActions;
+        this.mLauncher = launcherInstrumentation;
+    }
+
+    /**
+     * Clicks content push button.
+     */
+    @NonNull
+    public Overview clickAndDismissContentPush() {
+        if (mLauncher.overviewContentPushEnabled()) {
+            try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+                 LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                         "want to click content push button and exit screenshot ui")) {
+                UiObject2 exo = mLauncher.waitForObjectInContainer(mOverviewActions,
+                        "action_content_push");
+                mLauncher.clickLauncherObject(exo);
+                try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                        "clicked content push button")) {
+                    return new Overview(mLauncher);
+                }
+            }
+        }
+        return new Overview(mLauncher);
+    }
+
+    /**
+     * Clicks screenshot button and closes screenshot ui.
+     */
+    @NonNull
+    public Overview clickAndDismissScreenshot() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to click screenshot button and exit screenshot ui")) {
+            UiObject2 screenshot = mLauncher.waitForObjectInContainer(mOverviewActions,
+                    "action_screenshot");
+            mLauncher.clickLauncherObject(screenshot);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked screenshot button")) {
+                UiObject2 closeScreenshot = mLauncher.waitForSystemUiObject(
+                        "global_screenshot_dismiss_image");
+                if (mLauncher.getNavigationModel()
+                        != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
+                            LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
+                            LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+                }
+                closeScreenshot.click();
+                try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                        "dismissed screenshot")) {
+                    return new Overview(mLauncher);
+                }
+            }
+        }
+    }
+
+    /**
+     * Click share button, then drags sharesheet down to remove it.
+     *
+     * Share is currently hidden behind flag, test is kept in case share becomes a default feature.
+     * If share is completely removed then remove this test as well.
+     */
+    @NonNull
+    public Overview clickAndDismissShare() {
+        if (mLauncher.overviewShareEnabled()) {
+            try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+                 LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                         "want to click share button and dismiss sharesheet")) {
+                UiObject2 share = mLauncher.waitForObjectInContainer(mOverviewActions,
+                        "action_share");
+                mLauncher.clickLauncherObject(share);
+                try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                        "clicked share button")) {
+                    mLauncher.waitForAndroidObject("contentPanel");
+                    mLauncher.getDevice().pressBack();
+                    try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
+                            "dismissed sharesheet")) {
+                        return new Overview(mLauncher);
+                    }
+                }
+            }
+        }
+        return new Overview(mLauncher);
+    }
+
+    /**
+     * Click select button
+     *
+     * @return The select mode buttons that are now shown instead of action buttons.
+     */
+    @NonNull
+    public SelectModeButtons clickSelect() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click select button")) {
+            UiObject2 select = mLauncher.waitForObjectInContainer(mOverviewActions,
+                    "action_select");
+            mLauncher.clickLauncherObject(select);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked select button")) {
+                return getSelectModeButtons();
+            }
+
+        }
+    }
+
+    /**
+     * Gets the Select Mode Buttons.
+     *
+     * @return The Select Mode Buttons.
+     */
+    @NonNull
+    private SelectModeButtons getSelectModeButtons() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to get select mode buttons")) {
+            UiObject2 selectModeButtons = mLauncher.waitForLauncherObject("select_mode_buttons");
+            return new SelectModeButtons(selectModeButtons, mLauncher);
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
new file mode 100644
index 0000000..3507418
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
@@ -0,0 +1,68 @@
+/*
+ * 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.tapl;
+
+import androidx.annotation.NonNull;
+import androidx.test.uiautomator.UiObject2;
+
+/**
+ * View containing select mode buttons
+ */
+public class SelectModeButtons {
+    private final UiObject2 mSelectModeButtons;
+    private final LauncherInstrumentation mLauncher;
+
+    SelectModeButtons(UiObject2 selectModeButtons,
+            LauncherInstrumentation launcherInstrumentation) {
+        mSelectModeButtons = selectModeButtons;
+        mLauncher = launcherInstrumentation;
+    }
+
+    /**
+     * Click close button.
+     */
+    @NonNull
+    public Overview clickClose() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click close button")) {
+            UiObject2 close = mLauncher.waitForObjectInContainer(mSelectModeButtons, "close");
+            mLauncher.clickLauncherObject(close);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked close button")) {
+                return new Overview(mLauncher);
+            }
+        }
+    }
+
+    /**
+     * Click feedback button.
+     */
+    @NonNull
+    public Background clickFeedback() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c =
+                     mLauncher.addContextLayer("want to click feedback button")) {
+            UiObject2 feedback = mLauncher.waitForObjectInContainer(mSelectModeButtons, "feedback");
+            mLauncher.clickLauncherObject(feedback);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+                    "clicked feedback button")) {
+                return new Background(mLauncher);
+            }
+        }
+    }
+}