Merging from ub-launcher3-master @ build 6219992

Test: manual, presubmit on the source branch
http://x20/teams/android-launcher/merge/ub-launcher3-master_6219992.html

Change-Id: Iff1e0d32b4ded4cb51c91c187d85f128b92157ca
diff --git a/Android.bp b/Android.bp
index fc99880..cb695df 100644
--- a/Android.bp
+++ b/Android.bp
@@ -32,7 +32,7 @@
 }
 
 java_library_static {
-    name: "launcher-log-protos-lite",
+    name: "launcher_log_protos_lite",
     srcs: [
         "protos/*.proto",
         "proto_overrides/*.proto",
@@ -45,4 +45,5 @@
             "proto_overrides",
         ],
     },
+    static_libs: ["libprotobuf-java-lite"],
 }
diff --git a/Android.mk b/Android.mk
index a099ada..c066a12 100644
--- a/Android.mk
+++ b/Android.mk
@@ -48,7 +48,9 @@
     androidx.preference_preference \
     iconloader_base
 
-LOCAL_STATIC_JAVA_LIBRARIES := LauncherPluginLib
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    LauncherPluginLib \
+    launcher_log_protos_lite
 
 LOCAL_SRC_FILES := \
     $(call all-proto-files-under, protos) \
@@ -144,7 +146,10 @@
 LOCAL_AAPT2_ONLY := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    SystemUISharedLib \
+    launcherprotosnano \
+    launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
@@ -213,7 +218,10 @@
 LOCAL_USE_AAPT2 := true
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := SystemUISharedLibLauncherWrapper launcherprotosnano
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    SystemUISharedLib \
+    launcherprotosnano \
+    launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
 else
diff --git a/go/src/com/android/launcher3/model/WidgetsModel.java b/go/src/com/android/launcher3/model/WidgetsModel.java
index 7269b41..7690b9d 100644
--- a/go/src/com/android/launcher3/model/WidgetsModel.java
+++ b/go/src/com/android/launcher3/model/WidgetsModel.java
@@ -22,7 +22,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetListRowEntry;
 
@@ -59,7 +59,7 @@
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
      */
-    public List<ComponentWithLabel> update(LauncherAppState app,
+    public List<ComponentWithLabelAndIcon> update(LauncherAppState app,
             @Nullable PackageUserKey packageUser) {
         return Collections.emptyList();
     }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index bce4e0f..31a923e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -350,6 +350,17 @@
                 iconDpi);
     }
 
+    /**
+     * Badges the provided source with the badge info
+     */
+    public BitmapInfo badgeBitmap(Bitmap source, BitmapInfo badgeInfo) {
+        Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
+            getShadowGenerator().recreateIcon(source, c);
+            badgeWithDrawable(c, new FixedSizeBitmapDrawable(badgeInfo.icon));
+        });
+        return BitmapInfo.of(icon, badgeInfo.color);
+    }
+
     private int extractColor(Bitmap bitmap) {
         return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);
     }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index e807791..4c634cb 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -281,7 +281,8 @@
 
         ContentValues values = newContentValues(entry.bitmap, entry.title.toString(),
                 componentName.getPackageName(), cachingLogic.getKeywords(object, mLocaleList));
-        addIconToDB(values, componentName, info, userSerial);
+        addIconToDB(values, componentName, info, userSerial,
+                cachingLogic.getLastUpdatedTime(object, info));
     }
 
     /**
@@ -289,10 +290,10 @@
      * @param values {@link ContentValues} containing icon & title
      */
     private void addIconToDB(ContentValues values, ComponentName key,
-            PackageInfo info, long userSerial) {
+            PackageInfo info, long userSerial, long lastUpdateTime) {
         values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
         values.put(IconDB.COLUMN_USER, userSerial);
-        values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
+        values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime);
         values.put(IconDB.COLUMN_VERSION, info.versionCode);
         mIconDb.insertOrReplace(values);
     }
@@ -362,7 +363,8 @@
                 }
                 if (object != null) {
                     entry.title = cachingLogic.getLabel(object);
-                    entry.contentDescription = mPackageManager.getUserBadgedLabel(entry.title, user);
+                    entry.contentDescription = mPackageManager.getUserBadgedLabel(
+                            cachingLogic.getDescription(object, entry.title), user);
                 }
             }
         }
@@ -449,7 +451,8 @@
                     // package updates.
                     ContentValues values = newContentValues(
                             iconInfo, entry.title.toString(), packageName, null);
-                    addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user));
+                    addIconToDB(values, cacheKey.componentName, info, getSerialNumberForUser(user),
+                            info.lastUpdateTime);
 
                 } catch (NameNotFoundException e) {
                     if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index a89ede7..c12e9dc 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -34,6 +34,10 @@
 
     CharSequence getLabel(T object);
 
+    default CharSequence getDescription(T object, CharSequence fallback) {
+        return fallback;
+    }
+
     @NonNull
     BitmapInfo loadIcon(Context context, T object);
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index d0db157..9e1ad7b 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -24,6 +24,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseBooleanArray;
 
@@ -61,7 +62,7 @@
     private final HashMap<String, PackageInfo> mPkgInfoMap;
     private final BaseIconCache mIconCache;
 
-    private final HashMap<UserHandle, Set<String>> mPackagesToIgnore = new HashMap<>();
+    private final ArrayMap<UserHandle, Set<String>> mPackagesToIgnore = new ArrayMap<>();
 
     private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray();
     private boolean mFilterMode = MODE_SET_INVALID_ITEMS;
@@ -77,8 +78,16 @@
         createPackageInfoMap();
     }
 
-    public void setPackagesToIgnore(UserHandle userHandle, Set<String> packages) {
-        mPackagesToIgnore.put(userHandle, packages);
+    /**
+     * 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() {
@@ -180,6 +189,7 @@
                     }
                     continue;
                 }
+
                 if (app == null) {
                     if (mFilterMode == MODE_SET_INVALID_ITEMS) {
                         mIconCache.remove(component, user);
@@ -262,6 +272,7 @@
                 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);
diff --git a/proguard.flags b/proguard.flags
index 01302cf..e556c94 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -48,3 +48,6 @@
 -dontwarn android.view.**
 -dontwarn android.os.**
 -dontwarn android.graphics.**
+
+# Ignore warnings for hidden utility classes referenced from the shared lib
+-dontwarn com.android.internal.util.**
\ No newline at end of file
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
index 0560d68..ec1d55b 100644
--- a/protos/launcher_log.proto
+++ b/protos/launcher_log.proto
@@ -58,6 +58,35 @@
   optional TipType tip_type = 17;
   optional int32 search_query_length = 18;
   optional bool is_work_app = 19;
+  optional FromFolderLabelState from_folder_label_state = 20;
+  optional ToFolderLabelState to_folder_label_state = 21;
+
+  // Note: proto does not support duplicate enum values, even if they belong to different enum type.
+  // Hence "FROM" and "TO" prefix added.
+  enum FromFolderLabelState{
+    FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    FROM_EMPTY = 1;
+    FROM_CUSTOM = 2;
+    FROM_SUGGESTED = 3;
+  }
+
+  enum ToFolderLabelState{
+    TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
+    TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
+    TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 3;
+    TO_SUGGESTION2_WITH_VALID_PRIMARY = 4;
+    TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
+    TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
+    TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
+    TO_EMPTY_WITH_VALID_SUGGESTIONS = 8;
+    TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
+    TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
+    TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11;
+    TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
+    TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
+    UNCHANGED = 14;
+  }
 }
 
 // Used to define what type of item a Target would represent.
@@ -120,6 +149,8 @@
   BACK_GESTURE = 19;
   UNDO = 20;
   DISMISS_PREDICTION = 21;
+  HYBRID_HOTSEAT_ACCEPTED = 22;
+  HYBRID_HOTSEAT_CANCELED = 23;
 }
 
 enum TipType {
@@ -129,6 +160,7 @@
   QUICK_SCRUB_TEXT = 3;
   PREDICTION_TEXT = 4;
   DWB_TOAST = 5;
+  HYBRID_HOTSEAT = 6;
 }
 
 // Used to define the action component of the LauncherEvent.
@@ -138,7 +170,8 @@
     AUTOMATED = 1;
     COMMAND = 2;
     TIP = 3;
-    // SOFT_KEYBOARD, HARD_KEYBOARD, ASSIST
+    SOFT_KEYBOARD = 4;
+    // HARD_KEYBOARD, ASSIST
   }
 
   enum Touch {
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
index 76972af..54f58e2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -43,7 +43,6 @@
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.InstantAppResolver;
@@ -170,14 +169,7 @@
         List<ShortcutInfo> details = shortcutKey.buildRequest(mContext).query(ShortcutRequest.ALL);
         if (!details.isEmpty()) {
             WorkspaceItemInfo si = new WorkspaceItemInfo(details.get(0), mContext);
-            try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-                si.bitmap = li.createShortcutIcon(details.get(0), true /* badged */, null);
-            } catch (Exception e) {
-                if (DEBUG) {
-                    Log.e(TAG, "Error loading shortcut icon for " + shortcutKey.toString());
-                }
-                return null;
-            }
+            mIconCache.getShortcutIcon(si, details.get(0));
             return si;
         }
         if (DEBUG) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
index f82af62..834e6cf 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -296,7 +296,7 @@
                 predictedApp.container = LauncherSettings.Favorites.CONTAINER_PREDICTION;
                 predictedApps.add(predictedApp);
             } else {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     Log.e(TAG, "Predicted app not found: " + mapper);
                 }
             }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 4c87945..00e72b1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.hybridhotseat;
 
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.HYBRID_HOTSEAT_CANCELED;
+
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -32,6 +35,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.AbstractSlideInView;
@@ -95,6 +99,7 @@
         handleClose(true);
         mHotseatEduController.migrate();
         mHotseatEduController.finishOnboarding();
+        logUserAction(true);
         Toast.makeText(mLauncher, R.string.hotseat_items_migrated, Toast.LENGTH_LONG).show();
     }
 
@@ -102,6 +107,7 @@
         if (mHotseatEduController == null) return;
         Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
         mHotseatEduController.finishOnboarding();
+        logUserAction(false);
         handleClose(true);
     }
 
@@ -133,7 +139,28 @@
                 mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
     }
 
+    private void logUserAction(boolean migrated) {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TOUCH;
+        action.touch = LauncherLogProto.Action.Touch.TAP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
+                : HYBRID_HOTSEAT_CANCELED;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
 
+    private void logOnBoardingSeen() {
+        LauncherLogProto.Action action = new LauncherLogProto.Action();
+        LauncherLogProto.Target target = new LauncherLogProto.Target();
+        action.type = LauncherLogProto.Action.Type.TIP;
+        target.containerType = LauncherLogProto.ContainerType.TIP;
+        target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
+        LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
+        UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
+    }
     private void animateOpen() {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
@@ -165,6 +192,7 @@
             return;
         }
         mLauncher.getDragLayer().addView(this);
+        logOnBoardingSeen();
         animateOpen();
         for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
             WorkspaceItemInfo info = predictions.get(i);
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index cc6ec69..0b05427 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -109,6 +109,7 @@
     private AppPredictor mAppPredictor;
     private AllAppsStore mAllAppsStore;
     private AnimatorSet mIconRemoveAnimators;
+    private boolean mUIUpdatePaused = false;
 
     private HotseatEduController mHotseatEduController;
 
@@ -168,7 +169,7 @@
     }
 
     private void fillGapsWithPrediction(boolean animate, Runnable callback) {
-        if (!isReady() || mDragObject != null) {
+        if (!isReady() || mUIUpdatePaused || mDragObject != null) {
             return;
         }
         List<WorkspaceItemInfo> predictedApps = mapToWorkspaceItemInfo(mComponentKeyMappers);
@@ -250,6 +251,16 @@
     }
 
     /**
+     * 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() {
@@ -447,17 +458,40 @@
     /**
      * Unpins pinned app when it's converted into a folder
      */
-    public void folderCreatedFromIcon(ItemInfo info, FolderInfo folderInfo) {
+    public void folderCreatedFromWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+        if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+            return;
+        }
         AppTarget target = getAppTargetFromItemInfo(info);
-        if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && !isInHotseat(
-                info)) {
+        ViewGroup hotseatVG = mHotseat.getShortcutsAndWidgets();
+        ViewGroup firstScreenVG = mLauncher.getWorkspace().getScreenWithId(
+                Workspace.FIRST_SCREEN_ID).getShortcutsAndWidgets();
+
+        if (isInHotseat(folderInfo) && !getPinnedAppTargetsInViewGroup(hotseatVG).contains(
+                target)) {
             notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
-        } else if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
-                && folderInfo.screenId == Workspace.FIRST_SCREEN_ID && !isInFirstPage(info)) {
+        } else if (isInFirstPage(folderInfo) && !getPinnedAppTargetsInViewGroup(
+                firstScreenVG).contains(target)) {
             notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
         }
     }
 
+    /**
+     * Pins workspace item created when all folder items are removed but one
+     */
+    public void folderConvertedToWorkspaceItem(ItemInfo info, FolderInfo folderInfo) {
+        if (info.itemType != LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+            return;
+        }
+        AppTarget target = getAppTargetFromItemInfo(info);
+        if (isInHotseat(info)) {
+            notifyItemAction(target, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+        } else if (isInFirstPage(info)) {
+            notifyItemAction(target, APP_LOCATION_WORKSPACE, AppTargetEvent.ACTION_PIN);
+        }
+    }
+
+
     @Override
     public void onDragEnd() {
         if (mDragObject == null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index b2e1798..4bbb48c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -86,6 +86,7 @@
         mIsDrawingDot = true;
         int count = canvas.save();
         canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+        canvas.scale(1 + 2 * RING_EFFECT_RATIO, 1 + 2 * RING_EFFECT_RATIO);
         super.drawDotIfNecessary(canvas);
         canvas.restoreToCount(count);
         mIsDrawingDot = false;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index f6bc5ea..30a4e50 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -20,20 +20,24 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Gravity;
+import android.view.View;
+
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.hybridhotseat.HotseatPredictionController;
 import com.android.launcher3.popup.SystemShortcut;
@@ -162,6 +166,15 @@
     }
 
     @Override
+    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
+            @Nullable String sourceContainer) {
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.setPauseUIUpdate(true);
+        }
+        return super.startActivitySafely(v, intent, item, sourceContainer);
+    }
+
+    @Override
     protected void onActivityFlagsChanged(int changeBits) {
         super.onActivityFlagsChanged(changeBits);
 
@@ -170,16 +183,27 @@
                 && (getActivityFlags() & ACTIVITY_STATE_TRANSITION_ACTIVE) == 0) {
             onStateOrResumeChanged();
         }
+
+        if ((changeBits & ACTIVITY_STATE_STARTED) != 0 && mHotseatPredictionController != null
+                && (getActivityFlags() & ACTIVITY_STATE_USER_ACTIVE) == 0) {
+            mHotseatPredictionController.setPauseUIUpdate(false);
+        }
     }
 
     @Override
-    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
-            int screenId, int cellX, int cellY) {
-        FolderIcon fi =  super.addFolder(layout, info, container, screenId, cellX, cellY);
+    public void folderCreatedFromItem(Folder folder, WorkspaceItemInfo itemInfo) {
+        super.folderCreatedFromItem(folder, itemInfo);
         if (mHotseatPredictionController != null) {
-            mHotseatPredictionController.folderCreatedFromIcon(info, fi.getFolder().getInfo());
+            mHotseatPredictionController.folderCreatedFromWorkspaceItem(itemInfo, folder.getInfo());
         }
-        return fi;
+    }
+
+    @Override
+    public void folderConvertedToItem(Folder folder, WorkspaceItemInfo itemInfo) {
+        super.folderConvertedToItem(folder, itemInfo);
+        if (mHotseatPredictionController != null) {
+            mHotseatPredictionController.folderConvertedToWorkspaceItem(itemInfo, folder.getInfo());
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 644cfcb..a1c8378 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -33,6 +33,7 @@
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.graphics.Rect;
 import android.view.View;
@@ -125,7 +126,8 @@
 
     @Override
     public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
-        if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()) {
+        if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()
+                && removeShelfFromOverview(launcher)) {
             // Treat the QSB as part of the hotseat so they move together.
             return getHotseatScaleAndTranslation(launcher);
         }
@@ -158,7 +160,7 @@
         if (launcher.getDeviceProfile().isVerticalBarLayout()) {
             return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
         } else {
-            if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
                 return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
             }
 
@@ -229,6 +231,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
             builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+                    && removeShelfFromOverview(launcher)
                     ? OVERSHOOT_1_2
                     : OVERSHOOT_1_7;
             builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index 28fc3da..1b6d291 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -234,6 +234,7 @@
             public void adjustActivityControllerInterpolators() {
                 if (mAdjustInterpolatorsRunnable != null) {
                     mAdjustInterpolatorsRunnable.run();
+                    mAdjustInterpolatorsRunnable = null;
                 }
             }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 1b60404..345a147 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,7 +21,7 @@
 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.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -75,7 +75,6 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AppWindowAnimationHelper.TargetAlphaProvider;
 import com.android.quickstep.util.RectFSpringAnim;
-import com.android.quickstep.util.SharedApiCompat;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState;
 import com.android.quickstep.views.LiveTileOverlay;
@@ -918,6 +917,12 @@
             windowAnim.addAnimatorListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (mRecentsAnimationController == null) {
+                        // If the recents animation is interrupted, we still end the running
+                        // animation (not canceled) so this is still called. In that case, we can
+                        // skip doing any future work here for the current gesture.
+                        return;
+                    }
                     // Finalize the state and notify of the change
                     mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
                 }
@@ -939,6 +944,12 @@
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (mRecentsAnimationController == null) {
+                        // If the recents animation is interrupted, we still end the running
+                        // animation (not canceled) so this is still called. In that case, we can
+                        // skip doing any future work here for the current gesture.
+                        return;
+                    }
                     if (target == NEW_TASK && mRecentsView != null
                             && mRecentsView.getNextPage() == mRecentsView.getRunningTaskIndex()) {
                         // We are about to launch the current running task, so use LAST_TASK state
@@ -973,7 +984,7 @@
         }
         mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
 
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (UNSTABLE_SPRINGS.get()) {
             mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
         }
         mLauncherTransitionController.getAnimationPlayer().start();
@@ -1142,8 +1153,7 @@
         final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
-                SharedApiCompat.setWillFinishToHome(mRecentsAnimationController.getController(),
-                        true /* willFinishToHome */);
+                mRecentsAnimationController.getController().setWillFinishToHome(true);
                 // Update the screenshot of the task
                 if (mTaskSnapshot == null) {
                     mTaskSnapshot = mRecentsAnimationController.screenshotTask(runningTaskId);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 3e6def3..34b2bdb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,23 +1,18 @@
 package com.android.quickstep;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
+import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.testing.TestInformationHandler;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.quickstep.util.LayoutUtils;
-import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 
 public class QuickstepTestInformationHandler extends TestInformationHandler {
@@ -46,33 +41,8 @@
             }
 
             case TestProtocol.REQUEST_HOTSEAT_TOP: {
-                if (mLauncher == null) return null;
-
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                        PortraitStatesTouchController.getHotseatTop(mLauncher));
-                return response;
-            }
-
-            case TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN: {
-                try {
-                    final int leftMargin = MAIN_EXECUTOR.submit(() ->
-                            getRecentsView().getLeftGestureMargin()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, leftMargin);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return response;
-            }
-
-            case TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN: {
-                try {
-                    final int rightMargin = MAIN_EXECUTOR.submit(() ->
-                            getRecentsView().getRightGestureMargin()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, rightMargin);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                return response;
+                return getLauncherUIProperty(
+                        Bundle::putInt, PortraitStatesTouchController::getHotseatTop);
             }
 
             case TestProtocol.REQUEST_RECENT_TASKS_LIST: {
@@ -99,11 +69,12 @@
         return super.call(method);
     }
 
-    private RecentsView getRecentsView() {
+    @Override
+    protected Activity getCurrentActivity() {
         OverviewComponentObserver observer = new OverviewComponentObserver(mContext,
                 new RecentsAnimationDeviceState(mContext));
         try {
-            return observer.getActivityInterface().getCreatedActivity().getOverviewPanel();
+            return observer.getActivityInterface().getCreatedActivity();
         } finally {
             observer.onDestroy();
         }
@@ -111,11 +82,6 @@
 
     @Override
     protected boolean isLauncherInitialized() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "isLauncherInitialized.TouchInteractionService.isInitialized=" +
-                            TouchInteractionService.isInitialized());
-        }
         return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 6af6da3..5820029 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -142,16 +142,13 @@
                 TouchInteractionService.this.initInputMonitor();
                 preloadOverview(true /* fromInit */);
             });
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
-            }
             sIsInitialized = true;
         }
 
         @BinderThread
         @Override
         public void onOverviewToggle() {
-            TestLogging.recordEvent("onOverviewToggle");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle");
             mOverviewCommandHelper.onOverviewToggle();
         }
 
@@ -411,9 +408,6 @@
 
     @Override
     public void onDestroy() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed");
-        }
         sIsInitialized = false;
         if (mDeviceState.isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
@@ -444,13 +438,17 @@
             Log.e(TAG, "Unknown event " + ev);
             return;
         }
+        MotionEvent event = (MotionEvent) ev;
+
+        TestLogging.recordMotionEvent(
+                TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
+
         if (!mDeviceState.isUserUnlocked()) {
             return;
         }
 
         Object traceToken = TraceHelper.INSTANCE.beginFlagsOverride(
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
-        MotionEvent event = (MotionEvent) ev;
         if (event.getAction() == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
@@ -731,20 +729,15 @@
             }
         } else {
             // Dump everything
+            FeatureFlags.dump(pw);
+            PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
             mDeviceState.dump(pw);
+            mOverviewComponentObserver.dump(pw);
             pw.println("TouchState:");
             boolean resumed = mOverviewComponentObserver != null
                     && mOverviewComponentObserver.getActivityInterface().isResumed();
             pw.println("  resumed=" + resumed);
             pw.println("  mConsumer=" + mConsumer.getName());
-            pw.println("FeatureFlags:");
-            pw.println("  APPLY_CONFIG_AT_RUNTIME=" + APPLY_CONFIG_AT_RUNTIME.get());
-            pw.println("  QUICKSTEP_SPRINGS=" + QUICKSTEP_SPRINGS.get());
-            pw.println("  UNSTABLE_SPRINGS=" + UNSTABLE_SPRINGS.get());
-            pw.println("  ADAPTIVE_ICON_WINDOW_ANIM=" + ADAPTIVE_ICON_WINDOW_ANIM.get());
-            pw.println("  ENABLE_QUICKSTEP_LIVE_TILE=" + ENABLE_QUICKSTEP_LIVE_TILE.get());
-            pw.println("  ENABLE_HINTS_IN_OVERVIEW=" + ENABLE_HINTS_IN_OVERVIEW.get());
-            pw.println("  FAKE_LANDSCAPE_UI=" + FAKE_LANDSCAPE_UI.get());
             ActiveGestureLog.INSTANCE.dump("", pw);
             pw.println("ProtoTrace:");
             pw.println("  file="
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 8ae4f06..05c206f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -3,6 +3,7 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.quickstep.InputConsumer;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
@@ -35,7 +36,7 @@
 
     protected void setActive(MotionEvent ev) {
         mState = STATE_ACTIVE;
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitor.pilferPointers();
 
         // Send cancel event
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index d01e1a4..ba1d38c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -203,7 +204,7 @@
 
     private void startRecentsTransition() {
         mThresholdCrossed = true;
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         Intent intent = new Intent(Intent.ACTION_MAIN)
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 1b0e05a..3ee3c2d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -45,6 +45,7 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.BaseActivityInterface;
@@ -303,7 +304,7 @@
         if (mInteractionHandler == null) {
             return;
         }
-        TestLogging.recordEvent("pilferPointers");
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
 
         mActivityInterface.closeOverlay();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 710ce26..f8abba5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
@@ -107,7 +108,7 @@
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
-                TestLogging.recordEvent("pilferPointers");
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
                 mInputMonitor.pilferPointers();
             }
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 6bfc3fd..823b254 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -23,6 +23,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.testing.TestLogging;
+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.quickstep.GestureState;
@@ -64,7 +65,7 @@
 
     private void onInterceptTouch() {
         if (mInputMonitor != null) {
-            TestLogging.recordEvent("pilferPointers");
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "pilferPointers");
             mInputMonitor.pilferPointers();
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index cc10ca9..a673ab6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -73,7 +73,6 @@
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
-import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.ListView;
@@ -345,7 +344,7 @@
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
         mTaskTopMargin = getResources()
                 .getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-        mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
+        mTaskBottomMargin = LayoutUtils.thumbnailBottomMargin(context);
         mSquaredTouchSlop = squaredTouchSlop(context);
 
         mEmptyIcon = context.getDrawable(R.drawable.ic_empty_recents);
@@ -1251,7 +1250,7 @@
     }
 
     public PendingAnimation createAllTasksDismissAnimation(long duration) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
         AnimatorSet anim = new AnimatorSet();
@@ -1595,7 +1594,7 @@
     }
 
     public PendingAnimation createTaskLauncherAnimation(TaskView tv, long duration) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mPendingAnimation != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
 
@@ -1889,16 +1888,6 @@
         }
     }
 
-    public int getLeftGestureMargin() {
-        final WindowInsets insets = getRootWindowInsets();
-        return Math.max(insets.getSystemGestureInsets().left, insets.getSystemWindowInsetLeft());
-    }
-
-    public int getRightGestureMargin() {
-        final WindowInsets insets = getRootWindowInsets();
-        return Math.max(insets.getSystemGestureInsets().right, insets.getSystemWindowInsetRight());
-    }
-
     /** If it's in the live tile mode, switch the running task into screenshot mode. */
     public void switchToScreenshot(ThumbnailData thumbnailData, Runnable onFinishRunnable) {
         TaskView taskView = getRunningTaskView();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
index 49f667e..8ed1392 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -54,6 +54,7 @@
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ConfigurationCompat;
 
 /**
  * A task in the Recents view.
@@ -357,7 +358,8 @@
 
             final float thumbnailScale;
             int thumbnailRotation = mThumbnailData.rotation;
-            int currentRotation = getDisplay() != null ? getDisplay().getRotation() : 0;
+            int currentRotation = ConfigurationCompat.getWindowConfigurationRotation(
+                    getResources().getConfiguration());
             int deltaRotate = getRotationDelta(currentRotation, thumbnailRotation);
             // Landscape vs portrait change
             boolean windowingModeSupportsRotation = !mActivity.isInMultiWindowMode()
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 5954b86..8b7ce10 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -30,7 +31,6 @@
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Outline;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -58,6 +58,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 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;
@@ -221,7 +222,7 @@
         mCurrentFullscreenParams = new FullscreenDrawParams(mCornerRadius);
         mDigitalWellBeingToast = new DigitalWellBeingToast(mActivity, this);
 
-        mOutlineProvider = new TaskOutlineProvider(getResources(), mCurrentFullscreenParams);
+        mOutlineProvider = new TaskOutlineProvider(getContext(), mCurrentFullscreenParams);
         setOutlineProvider(mOutlineProvider);
     }
 
@@ -230,13 +231,14 @@
         super.onFinishInflate();
         mSnapshotView = findViewById(R.id.snapshot);
         mIconView = findViewById(R.id.icon);
+        final Context context = getContext();
 
         TaskView.LayoutParams thumbnailParams = (LayoutParams) mSnapshotView.getLayoutParams();
-        thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(getResources());
+        thumbnailParams.bottomMargin = LayoutUtils.thumbnailBottomMargin(context);
         mSnapshotView.setLayoutParams(thumbnailParams);
 
 
-        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             mActionsView = mSnapshotView.getTaskOverlay().getActionsView();
             if (mActionsView != null) {
                 TaskView.LayoutParams params = new TaskView.LayoutParams(LayoutParams.MATCH_PARENT,
@@ -333,9 +335,8 @@
             Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
         if (mTask != null) {
             final ActivityOptions opts;
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                TestLogging.recordEvent("startActivityFromRecentsAsync:" + mTask);
-            }
+            TestLogging.recordEvent(
+                    TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
             if (animate) {
                 opts = mActivity.getActivityLaunchOptions(this);
                 if (freezeTaskList) {
@@ -679,9 +680,10 @@
         private final int mMarginBottom;
         private FullscreenDrawParams mFullscreenParams;
 
-        TaskOutlineProvider(Resources res, FullscreenDrawParams fullscreenParams) {
-            mMarginTop = res.getDimensionPixelSize(R.dimen.task_thumbnail_top_margin);
-            mMarginBottom = LayoutUtils.thumbnailBottomMargin(res);
+        TaskOutlineProvider(Context context, FullscreenDrawParams fullscreenParams) {
+            mMarginTop = context.getResources().getDimensionPixelSize(
+                    R.dimen.task_thumbnail_top_margin);
+            mMarginBottom = LayoutUtils.thumbnailBottomMargin(context);
             mFullscreenParams = fullscreenParams;
         }
 
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 5f81e1f..45a62ab 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -67,13 +67,13 @@
     <string  name="back_gesture_tutorial_close_button_content_description" translatable="false">Close</string>
 
     <!-- Hotseat migration notification title -->
-    <string translatable="false" name="hotseat_migrate_prompt_title">Get suggested apps on the home screen</string>
+    <string translatable="false" name="hotseat_migrate_prompt_title">Easily access your most-used apps</string>
     <!-- Hotseat migration notification content -->
-    <string translatable="false" name="hotseat_migrate_prompt_content">Tap to set up</string>
+    <string translatable="false" name="hotseat_migrate_prompt_content">Pixel suggests your favorite apps based on your routines. Tap to learn more.</string>
     <!-- Hotseat migration wizard title -->
     <string translatable="false" name="hotseat_migrate_title">Suggested apps replace the bottom row of apps</string>
     <!-- Hotseat migration wizard message -->
-    <string translatable="false" name="hotseat_migrate_message">To pin a favorite app, drag it over a suggested app. To hide a suggested app, touch &amp; hold it.</string>
+    <string translatable="false" name="hotseat_migrate_message">Your current apps will move to the last screen. To pin or block a suggested app, drag it off the bottom row.</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
     <string translatable="false" name="hotseat_items_migrated">Bottom row of apps moved to last screen</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 8eb639b..4470407 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.allapps.DiscoveryBounce.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_COUNT;
 import static com.android.launcher3.allapps.DiscoveryBounce.SHELF_BOUNCE_SEEN;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -35,6 +36,7 @@
 
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
@@ -172,7 +174,7 @@
     @Override
     public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
         if (requestCode != -1) {
-            mPendingActivityRequestCode = -1;
+            mPendingActivityRequestCode = requestCode;
             StartActivityParams params = new StartActivityParams(this, requestCode);
             params.intent = intent;
             params.options = options;
@@ -194,6 +196,16 @@
     }
 
     @Override
+    protected void setupViews() {
+        super.setupViews();
+
+        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
+            // Overview is above all other launcher elements, including qsb, so move it to the top.
+            getOverviewPanel().bringToFront();
+        }
+    }
+
+    @Override
     protected StateHandler[] createStateHandlers() {
         return new StateHandler[] {
                 getAllAppsController(),
diff --git a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java b/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index 2d9a161..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,48 +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.launcher3.uioverrides;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.shared.system.RotationWatcher;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends RotationWatcher {
-
-    private final Runnable mCallback;
-    private Handler mHandler;
-
-    public DisplayRotationListener(Context context, Runnable callback) {
-        super(context);
-        mCallback = callback;
-    }
-
-    @Override
-    public void enable() {
-        if (mHandler == null) {
-            mHandler = new Handler();
-        }
-        super.enable();
-    }
-
-    @Override
-    protected void onRotationChanged(int i) {
-        mHandler.post(mCallback);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
index 6e7c087..2e422b7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/plugins/PluginManagerWrapper.java
@@ -14,8 +14,12 @@
 
 package com.android.launcher3.uioverrides.plugins;
 
+import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
+
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.plugins.Plugin;
@@ -24,6 +28,9 @@
 import com.android.systemui.shared.plugins.PluginManagerImpl;
 import com.android.systemui.shared.plugins.PluginPrefs;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 
 public class PluginManagerWrapper {
@@ -75,4 +82,27 @@
     public static boolean hasPlugins(Context context) {
         return PluginPrefs.hasPlugins(context);
     }
+
+    public void dump(PrintWriter pw) {
+        final List<ComponentName> enabledPlugins = new ArrayList<>();
+        final List<ComponentName> disabledPlugins = new ArrayList<>();
+        for (String action : getPluginActions()) {
+            for (ResolveInfo resolveInfo : mContext.getPackageManager().queryIntentServices(
+                    new Intent(action), MATCH_DISABLED_COMPONENTS)) {
+                ComponentName installedPlugin = new ComponentName(
+                        resolveInfo.serviceInfo.packageName, resolveInfo.serviceInfo.name);
+                if (mPluginEnabler.isEnabled(installedPlugin)) {
+                    enabledPlugins.add(installedPlugin);
+                } else {
+                    disabledPlugins.add(installedPlugin);
+                }
+            }
+        }
+
+        pw.println("PluginManager:");
+        pw.println("  numEnabledPlugins=" + enabledPlugins.size());
+        pw.println("  numDisabledPlugins=" + disabledPlugins.size());
+        pw.println("  enabledPlugins=" + enabledPlugins);
+        pw.println("  disabledPlugins=" + disabledPlugins);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 99b2a81..d5ce734 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
 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.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
@@ -277,7 +277,7 @@
     private void handleFirstSwipeToOverview(final ValueAnimator animator,
             final long expectedDuration, final LauncherState targetState, final float velocity,
             final boolean isFling) {
-        if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+        if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
                 && targetState == OVERVIEW) {
             mFinishFastOnSecondTouch = true;
         } else  if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index 6c65e01..b3875ae 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -130,7 +130,7 @@
             final LinkedList<Runnable> callbacks;
             if (mCallbacks.indexOfKey(stateMask) >= 0) {
                 callbacks = mCallbacks.get(stateMask);
-                if (FeatureFlags.IS_DOGFOOD_BUILD && callbacks.contains(callback)) {
+                if (FeatureFlags.IS_STUDIO_BUILD && callbacks.contains(callback)) {
                     throw new IllegalStateException("Existing callback for state found");
                 }
             } else {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 73b78db..85ef4c6 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -35,6 +35,7 @@
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
 
@@ -233,4 +234,13 @@
     public BaseActivityInterface getActivityInterface() {
         return mActivityInterface;
     }
+
+    public void dump(PrintWriter pw) {
+        pw.println("OverviewComponentObserver:");
+        pw.println("  isDefaultHome=" + mIsDefaultHome);
+        pw.println("  isHomeDisabled=" + mIsHomeDisabled);
+        pw.println("  homeAndOverviewSame=" + mIsHomeAndOverviewSame);
+        pw.println("  overviewIntent=" + mOverviewIntent);
+        pw.println("  homeIntent=" + mCurrentHomeIntent);
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 9817e32..32268a4 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,11 +15,6 @@
  */
 package com.android.quickstep;
 
-import static android.content.Context.MODE_PRIVATE;
-
-import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
-import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
-
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.UserManager;
@@ -27,7 +22,6 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
@@ -55,22 +49,7 @@
         super.init(context);
 
         // Elevate GPU priority for Quickstep and Remote animations.
-        ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
-
-        // Force disable some feature flags based on the system ui navigation mode.
-        SysUINavigationMode.Mode currMode = SysUINavigationMode.INSTANCE.get(context)
-                .addModeChangeListener(mode -> disableFeatureFlagsForSysuiNavMode(context, mode));
-        disableFeatureFlagsForSysuiNavMode(context, currMode);
-    }
-
-    private void disableFeatureFlagsForSysuiNavMode(Context ctx, SysUINavigationMode.Mode mode) {
-        if (mode == SysUINavigationMode.Mode.TWO_BUTTONS) {
-            ctx.getSharedPreferences(FLAGS_PREF_NAME, MODE_PRIVATE)
-                    .edit()
-                    .putBoolean(ENABLE_OVERVIEW_ACTIONS.key, false)
-                    .apply();
-
-            FeatureFlags.initialize(ctx);
-        }
+        ThreadedRendererCompat.setContextPriority(
+                ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index acf61b4..0f98b32 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -62,6 +62,12 @@
         mListeners.remove(listener);
     }
 
+    @UiThread
+    public void removeAllListeners() {
+        Preconditions.assertUIThread();
+        mListeners.clear();
+    }
+
     public void notifyAnimationCanceled() {
         mCancelled = true;
         onAnimationCanceled(null);
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index d3be0b9..21a4918 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -198,6 +198,7 @@
         if (mInputConsumerController != null) {
             mInputConsumerController.setInputListener(null);
         }
+        mInputProxySupplier = null;
     }
 
     private boolean onInputConsumerEvent(InputEvent ev) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d0afb21..4b33d21 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -180,7 +180,7 @@
             mDefaultDisplay.addChangeListener(this);
         }
 
-        if (mMode == NO_BUTTON) {
+        if (newMode == NO_BUTTON) {
             mExclusionListener.register();
         } else {
             mExclusionListener.unregister();
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 388b323..375e589 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -122,6 +122,12 @@
         }
     }
 
+    /** @return Whether we can remove the shelf from overview. */
+    public static boolean removeShelfFromOverview(Context context) {
+        // The shelf is core to the two-button mode model, so we need to continue supporting it.
+        return getMode(context) != Mode.TWO_BUTTONS;
+    }
+
     public interface NavigationModeChangeListener {
 
         void onNavigationModeChanged(Mode newMode);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index d9cb240..458d6a9 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -29,7 +29,6 @@
 import android.view.MotionEvent;
 
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.quickstep.util.SharedApiCompat;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 /**
@@ -278,7 +277,7 @@
             mLastShelfVisible = visible;
             mLastShelfHeight = shelfHeight;
             try {
-                SharedApiCompat.setShelfHeight(mSystemUiProxy, visible, shelfHeight);
+                mSystemUiProxy.setShelfHeight(visible, shelfHeight);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call setShelfHeight visible: " + visible
                         + " height: " + shelfHeight, e);
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index e3e8ace..6902e37 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -55,7 +55,7 @@
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new IllegalArgumentException(msg);
             } else {
                 Log.e("TaskAnimationManager", msg, new Exception());
@@ -156,9 +156,9 @@
             mTargets.release();
         }
 
-        // Remove gesture state from callbacks
-        if (mCallbacks != null && mLastGestureState != null) {
-            mCallbacks.removeListener(mLastGestureState);
+        // Clean up all listeners to ensure we don't get subsequent callbacks
+        if (mCallbacks != null) {
+            mCallbacks.removeAllListeners();
         }
 
         mController = null;
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
index 295ab48..d2a0951 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java
@@ -16,7 +16,10 @@
 package com.android.quickstep.interaction;
 
 import android.graphics.Color;
+import android.graphics.Rect;
 import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.View;
 import android.view.Window;
 
@@ -26,6 +29,7 @@
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep;
 import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType;
 
+import java.util.List;
 import java.util.Optional;
 
 /** Shows the Back gesture interactive tutorial in full screen mode. */
@@ -47,6 +51,12 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        disableSystemGestures();
+    }
+
+    @Override
     public void onWindowFocusChanged(boolean hasFocus) {
         super.onWindowFocusChanged(hasFocus);
         if (hasFocus) {
@@ -70,4 +80,14 @@
                         | View.SYSTEM_UI_FLAG_FULLSCREEN);
         getWindow().setNavigationBarColor(Color.TRANSPARENT);
     }
+
+    private void disableSystemGestures() {
+        Display display = getDisplay();
+        if (display != null) {
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            getWindow().setSystemGestureExclusionRects(
+                    List.of(new Rect(0, 0, metrics.widthPixels, metrics.heightPixels)));
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/BinderTracker.java b/quickstep/src/com/android/quickstep/util/BinderTracker.java
index 32d0d53..cb04e5b 100644
--- a/quickstep/src/com/android/quickstep/util/BinderTracker.java
+++ b/quickstep/src/com/android/quickstep/util/BinderTracker.java
@@ -31,7 +31,7 @@
     private static final String TAG = "BinderTracker";
 
     public static void start() {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
             return;
         }
@@ -40,7 +40,7 @@
     }
 
     public static void stop() {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             Log.wtf(TAG, "Accessing tracker in released code.", new Exception());
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index b249f48..1d8a79f 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -16,6 +16,7 @@
 package com.android.quickstep.util;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
@@ -59,7 +60,7 @@
         } else {
             Resources res = context.getResources();
 
-            if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
                 //TODO: this needs to account for the swipe gesture height and accessibility
                 // UI when shown.
                 extraSpace = 0;
@@ -83,6 +84,7 @@
         float taskWidth, taskHeight, paddingHorz;
         Resources res = context.getResources();
         Rect insets = dp.getInsets();
+        final boolean overviewActionsEnabled = ENABLE_OVERVIEW_ACTIONS.get();
 
         if (dp.isMultiWindowMode) {
             if (multiWindowStrategy == MULTI_WINDOW_STRATEGY_HALF_SCREEN) {
@@ -112,7 +114,7 @@
             final int paddingResId;
             if (dp.isVerticalBarLayout()) {
                 paddingResId = R.dimen.landscape_task_card_horz_space;
-            } else if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            } else if (overviewActionsEnabled && removeShelfFromOverview(context)) {
                 paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
             } else {
                 paddingResId = R.dimen.portrait_task_card_horz_space;
@@ -120,9 +122,11 @@
             paddingHorz = res.getDimension(paddingResId);
         }
 
-        float topIconMargin =   res.getDimension(R.dimen.task_thumbnail_top_margin);
-        float bottomMargin = thumbnailBottomMargin(res);
-        float paddingVert = res.getDimension(R.dimen.task_card_vert_space);
+        float topIconMargin = res.getDimension(R.dimen.task_thumbnail_top_margin);
+        float bottomMargin = thumbnailBottomMargin(context);
+
+        float paddingVert = overviewActionsEnabled && removeShelfFromOverview(context)
+                ? 0 : res.getDimension(R.dimen.task_card_vert_space);
 
         // Note this should be same as dp.availableWidthPx and dp.availableHeightPx unless
         // we override the insets ourselves.
@@ -140,14 +144,14 @@
         // Center in the visible space
         float x = insets.left + (launcherVisibleWidth - outWidth) / 2;
         float y = insets.top + Math.max(topIconMargin,
-                (launcherVisibleHeight - extraVerticalSpace - outHeight) / 2);
+                (launcherVisibleHeight - extraVerticalSpace - outHeight - bottomMargin) / 2);
         outRect.set(Math.round(x), Math.round(y),
                 Math.round(x) + Math.round(outWidth), Math.round(y) + Math.round(outHeight));
     }
 
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
         // Track the bottom of the window.
-        if (ENABLE_OVERVIEW_ACTIONS.get()) {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
             Rect taskSize = new Rect();
             calculateLauncherTaskSize(context, dp, taskSize);
             return (dp.heightPx - taskSize.height()) / 2;
@@ -162,9 +166,9 @@
      * Get the margin that the task thumbnail view should use.
      * @return the margin in pixels.
      */
-    public static int thumbnailBottomMargin(Resources resources) {
-        if (ENABLE_OVERVIEW_ACTIONS.get()) {
-            return resources.getDimensionPixelSize(R.dimen.overview_actions_height);
+    public static int thumbnailBottomMargin(Context context) {
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(context)) {
+            return context.getResources().getDimensionPixelSize(R.dimen.overview_actions_height);
         } else {
             return 0;
         }
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 7ed1e21..1ce3549 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -160,7 +160,8 @@
             Context context = getContext();
             if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
                 mDragHandleProgress = 1;
-                if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+                if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()
+                        && 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);
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 97424bb..1229a63 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -197,7 +197,8 @@
 
         Wait.atMost(() -> "Switching nav mode: "
                         + launcher.getNavigationModeMismatchError(),
-                () -> launcher.getNavigationModeMismatchError() == null, WAIT_TIME_MS, launcher);
+                () -> launcher.getNavigationModeMismatchError() == null,
+                60000 /* b/148422894 */, launcher);
 
         return true;
     }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index d2f5d8f..724af66 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -25,7 +25,6 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Intent;
-import android.os.RemoteException;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -48,7 +47,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -84,17 +82,6 @@
     }
 
     @Test
-    @PortraitLandscape
-    @Ignore // Enable after b/131115533
-    public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
-        mDevice.pressRecentApps();
-        waitForState("Launcher internal state didn't switch to Overview",
-                () -> LauncherState.OVERVIEW);
-
-        assertNotNull("getOverview() returned null", mLauncher.getOverview());
-    }
-
-    @Test
     @NavigationModeSwitch
     @PortraitLandscape
     public void testWorkspaceSwitchToAllApps() {
@@ -271,10 +258,8 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    // b/143285809 Remove @Stability on 02/27/20 if the test doesn't flake.
     @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
-    // b/143285809
-    @Ignore
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
         startTestActivity(3);
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index 893d796..923352e 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -46,9 +46,9 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_gravity="center_vertical"
+            style="@style/TextHeadline"
             android:layout_weight="1"
             android:background="@android:color/transparent"
-            android:fontFamily="sans-serif-condensed"
             android:textStyle="bold"
             android:gravity="center_horizontal"
             android:hint="@string/folder_hint_text"
@@ -58,7 +58,7 @@
             android:singleLine="true"
             android:textColor="?attr/folderTextColor"
             android:textColorHighlight="?android:attr/colorControlHighlight"
-            android:textColorHint="?attr/folderTextColor"
+            android:textColorHint="?attr/folderHintColor"
             android:textSize="@dimen/folder_label_text_size" />
 
         <com.android.launcher3.pageindicators.PageIndicatorDots
diff --git a/res/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 5607e78..33502d0 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -23,14 +23,14 @@
     <ImageView
         android:id="@+id/icon"
         android:contentDescription="@string/work_apps_paused_title"
-        android:layout_width="48dp"
-        android:layout_height="48dp"
-        android:tint="?attr/folderTextColor"
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:tint="?attr/workProfileOverlayTextColor"
         android:src="@drawable/ic_corp_off" />
 
     <TextView
         style="@style/TextHeadline"
-        android:textColor="?attr/folderTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:id="@+id/work_apps_paused_title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
@@ -43,7 +43,7 @@
     <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:textColor="?attr/folderTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:text="@string/work_apps_paused_body"
         android:textAlignment="center"
         android:textSize="16sp" />
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index db95416..2cffedd 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -32,6 +32,8 @@
         android:layout_weight="1"
         android:drawableStart="@drawable/ic_corp"
         android:drawablePadding="3dp"
+        android:drawableTint="?attr/workProfileOverlayTextColor"
+        android:textColor="?attr/workProfileOverlayTextColor"
         android:layout_height="wrap_content"
         android:ellipsize="end"
         android:gravity="center_vertical"
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index de17eb7..5a15ec6 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -40,6 +40,8 @@
     <attr name="folderIconRadius" format="float" />
     <attr name="folderIconBorderColor" format="color" />
     <attr name="folderTextColor" format="color" />
+    <attr name="folderHintColor" format="color" />
+    <attr name="workProfileOverlayTextColor" format="color" />
 
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
@@ -115,6 +117,7 @@
         <attr name="numFolderColumns" format="integer" />
         <!-- numHotseatIcons defaults to numColumns, if not specified -->
         <attr name="numHotseatIcons" format="integer" />
+        <attr name="dbFile" format="string" />
         <attr name="defaultLayoutId" format="reference" />
         <attr name="demoModeLayoutId" format="reference" />
     </declare-styleable>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bfa92f7..0775c0c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -142,7 +142,7 @@
     <string name="uninstall_system_app_text">This is a system app and can\'t be uninstalled.</string>
 
     <!-- Default folder title -->
-    <string name="folder_hint_text">Unnamed Folder</string>
+    <string name="folder_hint_text">Edit Name</string>
 
     <!-- Accessibility -->
     <!-- The format string for when an app is temporarily disabled. -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 35ae49c..cee268b 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -48,7 +48,9 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">?android:attr/colorPrimary</item>
         <item name="folderTextColor">#FF212121</item>
+        <item name="folderHintColor">#FF616161</item>
         <item name="loadingIconColor">#CCFFFFFF</item>
+        <item name="workProfileOverlayTextColor">#FF212121</item>
 
         <item name="android:windowTranslucentStatus">false</item>
         <item name="android:windowTranslucentNavigation">false</item>
@@ -76,6 +78,7 @@
         <item name="folderFillColor">#CDFFFFFF</item>
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">?attr/workspaceTextColor</item>
+
     </style>
 
     <style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
@@ -96,8 +99,10 @@
         <item name="folderFillColor">#DD3C4043</item> <!-- 87% GM2 800 -->
         <item name="folderIconBorderColor">#FF80868B</item>
         <item name="folderTextColor">@android:color/white</item>
+        <item name="folderHintColor">#FFCCCCCC</item>
         <item name="isMainColorDark">true</item>
         <item name="loadingIconColor">#99FFFFFF</item>
+        <item name="workProfileOverlayTextColor">@android:color/white</item>
     </style>
 
     <style name="LauncherTheme.Dark.DarkMainColor" parent="@style/LauncherTheme.Dark">
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 82547d5..1c99dfc 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -24,6 +24,7 @@
         launcher:numFolderRows="2"
         launcher:numFolderColumns="3"
         launcher:numHotseatIcons="3"
+        launcher:dbFile="launcher_3_by_3.db"
         launcher:defaultLayoutId="@xml/default_workspace_3x3" >
 
         <display-option
@@ -51,6 +52,7 @@
         launcher:numFolderRows="3"
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="4"
+        launcher:dbFile="launcher_4_by_4.db"
         launcher:defaultLayoutId="@xml/default_workspace_4x4" >
 
         <display-option
@@ -102,6 +104,7 @@
         launcher:numFolderRows="4"
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
+        launcher:dbFile="launcher.db"
         launcher:defaultLayoutId="@xml/default_workspace_5x5" >
 
         <display-option
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 6059981..7c7e73c 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -52,7 +52,7 @@
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 LOCAL_TEST_PACKAGE := Launcher3
-LOCAL_INSTRUMENT_SOURCE_DIRS := $(dir $(LOCAL_PATH))../src
+LOCAL_INSTRUMENT_SOURCE_DIRS := packages/apps/Launcher3/src
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index f769055..74ef55e 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.launcher3.folder;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -61,15 +61,16 @@
         ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
         list.add(mItem1);
         list.add(mItem2);
-        String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[0].equals("Work"));
+        FolderNameInfo[] nameInfos =
+                new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[0].getLabel());
 
-        suggestedNameOut[0] = "candidate1";
-        suggestedNameOut[1] = "candidate2";
-        suggestedNameOut[2] = "candidate3";
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, suggestedNameOut);
-        assertTrue(suggestedNameOut[3].equals("Work"));
+        nameInfos[0] = new FolderNameInfo("candidate1", 0.9);
+        nameInfos[1] = new FolderNameInfo("candidate2", 0.8);
+        nameInfos[2] = new FolderNameInfo("candidate3", 0.7);
+        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        assertEquals("Work", nameInfos[3].getLabel());
 
     }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index 48b5a45..95a4146 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -49,8 +49,9 @@
     @After
     public void tearDown() {
         // Clear existing logs
-        new File(mTempDir, "log-0").delete();
-        new File(mTempDir, "log-1").delete();
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).delete();
+        }
         mTempDir.delete();
 
         mTestActive = false;
@@ -89,8 +90,9 @@
 
         Calendar threeDaysAgo = Calendar.getInstance();
         threeDaysAgo.add(Calendar.HOUR, -72);
-        new File(mTempDir, "log-0").setLastModified(threeDaysAgo.getTimeInMillis());
-        new File(mTempDir, "log-1").setLastModified(threeDaysAgo.getTimeInMillis());
+        for (int i = 0; i < FileLog.LOG_DAYS; i++) {
+            new File(mTempDir, "log-" + i).setLastModified(threeDaysAgo.getTimeInMillis());
+        }
 
         FileLog.print("Testing", "abracadabra", new Exception("cat! cat!"));
         writer = new StringWriter();
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index f3c5191..217a41c 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.logging.UserEventDispatcher.UserEventDelegate;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
@@ -330,9 +331,7 @@
             return;
         }
         try {
-            if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                TestLogging.recordEvent("start: shortcut: " + packageName);
-            }
+            TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: shortcut", packageName);
             getSystemService(LauncherApps.class).startShortcut(packageName, id, sourceBounds,
                     startActivityOptions, user);
         } catch (SecurityException | IllegalStateException e) {
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index dda38b3..9f3b48f 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
+
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
@@ -37,9 +39,12 @@
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.AppLaunchTracker;
 import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.DisplayRotationListener;
 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.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
@@ -48,7 +53,7 @@
  * Extension of BaseActivity allowing support for drag-n-drop
  */
 public abstract class BaseDraggingActivity extends BaseActivity
-        implements WallpaperColorInfo.OnChangeListener {
+        implements WallpaperColorInfo.OnChangeListener, DisplayInfoChangeListener {
 
     private static final String TAG = "BaseDraggingActivity";
 
@@ -63,8 +68,6 @@
 
     private int mThemeRes = R.style.AppTheme;
 
-    private DisplayRotationListener mRotationListener;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -72,7 +75,7 @@
 
         mIsSafeModeEnabled = TraceHelper.whitelistIpcs("isSafeMode",
                 () -> getPackageManager().isSafeMode());
-        mRotationListener = new DisplayRotationListener(this, this::onDeviceRotationChanged);
+        DefaultDisplay.INSTANCE.get(this).addChangeListener(this);
 
         // Update theme
         WallpaperColorInfo.INSTANCE.get(this).addOnChangeListener(this);
@@ -167,9 +170,7 @@
                 startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
             } else if (user == null || user.equals(Process.myUserHandle())) {
                 // Could be launching some bookkeeping activity
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                    TestLogging.recordEvent("start: activity: " + intent);
-                }
+                TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "start: activity", intent);
                 startActivity(intent, optsBundle);
                 AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                         Process.myUserHandle(), sourceContainer);
@@ -238,7 +239,7 @@
     protected void onDestroy() {
         super.onDestroy();
         WallpaperColorInfo.INSTANCE.get(this).removeOnChangeListener(this);
-        mRotationListener.disable();
+        DefaultDisplay.INSTANCE.get(this).removeChangeListener(this);
     }
 
     public void runOnceOnStart(Runnable action) {
@@ -251,15 +252,13 @@
 
     protected void onDeviceProfileInitiated() {
         if (mDeviceProfile.isVerticalBarLayout()) {
-            mRotationListener.enable();
             mDeviceProfile.updateIsSeascape(this);
-        } else {
-            mRotationListener.disable();
         }
     }
 
-    private void onDeviceRotationChanged() {
-        if (mDeviceProfile.updateIsSeascape(this)) {
+    @Override
+    public void onDisplayInfoChanged(Info info, int flags) {
+        if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.updateIsSeascape(this)) {
             reapplyUi();
         }
     }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index ac61c49..8718820 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -538,7 +538,7 @@
         try {
             dispatchRestoreInstanceState(states);
         } catch (IllegalArgumentException ex) {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw ex;
             }
             // Mismatched viewId / viewType preventing restore. Skip restore on production builds.
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0d71da4..c049069 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -271,7 +271,7 @@
         // In multi-window mode, we can have widthPx = availableWidthPx
         // and heightPx = availableHeightPx because Launcher uses the InvariantDeviceProfiles'
         // widthPx and heightPx values where it's needed.
-        DeviceProfile profile = new DeviceProfile(context, inv, originalIdp, mwSize, mwSize,
+        DeviceProfile profile = new DeviceProfile(context, inv, null, mwSize, mwSize,
                 mwSize.x, mwSize.y, isLandscape, true);
 
         // If there isn't enough vertical cell padding with the labels displayed, hide the labels.
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index 466b7b2..f07040d 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -96,7 +96,7 @@
             }
 
             if (!(v.getParent() instanceof ShortcutAndWidgetContainer)) {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     throw new IllegalStateException("Parent of the focused item is not supported.");
                 } else {
                     return false;
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 787eee1..336e423 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -48,6 +48,8 @@
 
     public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
 
+    public static final String EXTRA_FOLDER_SUGGESTIONS = "suggest";
+
     public int options;
 
     public Intent suggestedFolderNames;
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index 3eb02b3..a5142d8 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
 
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -44,7 +43,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.LauncherIcons;
@@ -61,7 +59,6 @@
 
 import java.net.URISyntaxException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -491,13 +488,8 @@
                 return Pair.create(si, null);
             } else if (shortcutInfo != null) {
                 WorkspaceItemInfo itemInfo = new WorkspaceItemInfo(shortcutInfo, mContext);
-                if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-                    fetchAndUpdateShortcutIconAsync(mContext, itemInfo, shortcutInfo, true);
-                } else {
-                    LauncherIcons li = LauncherIcons.obtain(mContext);
-                    itemInfo.bitmap = li.createShortcutIcon(shortcutInfo);
-                    li.recycle();
-                }
+                LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(
+                        itemInfo, shortcutInfo);
                 return Pair.create(itemInfo, shortcutInfo);
             } else if (providerInfo != null) {
                 LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index a807e4f..857db8e 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -119,6 +119,7 @@
      */
     public int numAllAppsColumns;
 
+    public String dbFile;
     public int defaultLayoutId;
     int demoModeLayoutId;
 
@@ -146,6 +147,7 @@
         iconTextSize = p.iconTextSize;
         numHotseatIcons = p.numHotseatIcons;
         numAllAppsColumns = p.numAllAppsColumns;
+        dbFile = p.dbFile;
         defaultLayoutId = p.defaultLayoutId;
         demoModeLayoutId = p.demoModeLayoutId;
         mExtraAttrs = p.mExtraAttrs;
@@ -292,6 +294,7 @@
         numRows = closestProfile.numRows;
         numColumns = closestProfile.numColumns;
         numHotseatIcons = closestProfile.numHotseatIcons;
+        dbFile = closestProfile.dbFile;
         defaultLayoutId = closestProfile.defaultLayoutId;
         demoModeLayoutId = closestProfile.demoModeLayoutId;
         numFolderRows = closestProfile.numFolderRows;
@@ -559,6 +562,7 @@
 
         private final int numHotseatIcons;
 
+        private final String dbFile;
         private final int defaultLayoutId;
         private final int demoModeLayoutId;
 
@@ -571,6 +575,7 @@
             numRows = a.getInt(R.styleable.GridDisplayOption_numRows, 0);
             numColumns = a.getInt(R.styleable.GridDisplayOption_numColumns, 0);
 
+            dbFile = a.getString(R.styleable.GridDisplayOption_dbFile);
             defaultLayoutId = a.getResourceId(
                     R.styleable.GridDisplayOption_defaultLayoutId, 0);
             demoModeLayoutId = a.getResourceId(
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 80f8ae2..7e8377a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -100,6 +100,7 @@
 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.graphics.RotationMode;
@@ -1155,7 +1156,7 @@
     /**
      * Finds all the views we need and configure them properly.
      */
-    private void setupViews() {
+    protected void setupViews() {
         mDragLayer = findViewById(R.id.drag_layer);
         mFocusHandler = mDragLayer.getFocusIndicatorHelper();
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
@@ -1191,11 +1192,6 @@
         mDropTargetBar.setup(mDragController);
 
         mAllAppsController.setupViews(mAppsView);
-
-        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
-            // Overview is above all other launcher elements, including qsb, so move it to the top.
-            mOverviewPanel.bringToFront();
-        }
     }
 
     /**
@@ -1745,8 +1741,8 @@
     /**
      * Creates and adds new folder to CellLayout
      */
-    public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
-            final int screenId, int cellX, int cellY) {
+    public FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
+            int cellY) {
         final FolderInfo folderInfo = new FolderInfo();
         folderInfo.title = "";
 
@@ -1764,6 +1760,16 @@
     }
 
     /**
+     * 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.
@@ -1805,17 +1811,13 @@
 
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            TestLogging.recordEvent("Key event: " + event);
-        }
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Key event", event);
         return (event.getKeyCode() == KeyEvent.KEYCODE_HOME) || super.dispatchKeyEvent(event);
     }
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (Utilities.IS_RUNNING_IN_TEST_HARNESS && ev.getAction() != MotionEvent.ACTION_MOVE) {
-            TestLogging.recordEvent("Touch event: " + ev);
-        }
+        TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
         return super.dispatchTouchEvent(ev);
     }
 
@@ -2164,7 +2166,7 @@
                     Object tag = v.getTag();
                     String desc = "Collision while binding workspace item: " + item
                             + ". Collides with " + tag;
-                    if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    if (FeatureFlags.IS_STUDIO_BUILD) {
                         throw (new RuntimeException(desc));
                     } else {
                         Log.d(TAG, desc);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4cd038d..d77285d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -91,7 +91,7 @@
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             mModelChangeReceiver.register(mContext, ACTION_FORCE_ROLOAD);
         }
 
diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
index 228c07e..56cce78 100644
--- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java
@@ -7,10 +7,12 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Parcel;
 import android.os.UserHandle;
 
-import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.IconCache;
 
 /**
  * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords
@@ -19,7 +21,7 @@
  * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo}
  */
 public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo
-        implements ComponentWithLabel {
+        implements ComponentWithLabelAndIcon {
 
     public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-";
 
@@ -111,4 +113,9 @@
     public final UserHandle getUser() {
         return getProfile();
     }
+
+    @Override
+    public Drawable getFullResIcon(IconCache cache) {
+        return cache.getFullResIcon(provider.getPackageName(), icon);
+    }
 }
diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java
index 9c4646b..25afb55 100644
--- a/src/com/android/launcher3/LauncherFiles.java
+++ b/src/com/android/launcher3/LauncherFiles.java
@@ -15,8 +15,13 @@
     private static final String XML = ".xml";
 
     public static final String LAUNCHER_DB = "launcher.db";
+    public static final String LAUNCHER_4_BY_4_DB = "launcher_4_by_4.db";
+    public static final String LAUNCHER_3_BY_3_DB = "launcher_3_by_3.db";
+    public static final String LAUNCHER_2_BY_2_DB = "launcher_2_by_2.db";
+    public static final String BACKUP_DB = "backup.db";
     public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs";
-    public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs";
+    public static final String MANAGED_USER_PREFERENCES_KEY =
+            "com.android.launcher3.managedusers.prefs";
     // This preference file is not backed up to cloud.
     public static final String DEVICE_PREFERENCES_KEY = "com.android.launcher3.device.prefs";
 
@@ -25,6 +30,10 @@
 
     public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList(
             LAUNCHER_DB,
+            LAUNCHER_4_BY_4_DB,
+            LAUNCHER_3_BY_3_DB,
+            LAUNCHER_2_BY_2_DB,
+            BACKUP_DB,
             SHARED_PREFERENCES_KEY + XML,
             WIDGET_PREVIEWS_DB,
             MANAGED_USER_PREFERENCES_KEY + XML,
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 04a7ecd..f618fe1 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -17,7 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
-import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
+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;
@@ -37,7 +37,6 @@
 
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.AddWorkspaceItemsTask;
 import com.android.launcher3.model.AllAppsList;
@@ -56,7 +55,6 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LooperExecutor;
@@ -68,7 +66,6 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Optional;
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.Executor;
 import java.util.function.Supplier;
@@ -97,10 +94,6 @@
     private boolean mModelLoaded;
     public boolean isModelLoaded() {
         synchronized (mLock) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask);
-            }
             return mModelLoaded && mLoaderTask == null;
         }
     }
@@ -133,16 +126,6 @@
     }
 
     /**
-     * Returns AppInfo with corresponding package name.
-     * TODO: move to enqueueModelTask
-     */
-    public Optional<AppInfo> getAppInfoByPackageName(String pkg) {
-        return mBgAllAppsList.data.stream()
-                .filter(info -> info.componentName.getPackageName().equals(pkg))
-                .findAny();
-    }
-
-    /**
      * Adds the provided items to the workspace.
      */
     public void addAndBindAddedWorkspaceItems(List<Pair<ItemInfo, Object>> itemList) {
@@ -255,7 +238,7 @@
                     enqueueModelUpdateTask(new UserLockStateChangedTask(user));
                 }
             }
-        } else if (IS_DOGFOOD_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
+        } else if (IS_STUDIO_BUILD && ACTION_FORCE_ROLOAD.equals(action)) {
             for (Callbacks cb : getCallbacks()) {
                 if (cb instanceof Launcher) {
                     ((Launcher) cb).recreate();
@@ -373,9 +356,6 @@
     public boolean stopLoader() {
         synchronized (mLock) {
             LoaderTask oldTask = mLoaderTask;
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "LauncherModel.stopLoader");
-            }
             mLoaderTask = null;
             if (oldTask != null) {
                 oldTask.stopLocked();
@@ -389,10 +369,6 @@
         synchronized (mLock) {
             stopLoader();
             mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "LauncherModel.startLoaderForResults " + mLoaderTask);
-            }
 
             // Always post the loader task, instead of running directly (even on same thread) so
             // that we exit any nested synchronized blocks
@@ -494,10 +470,6 @@
         public void close() {
             synchronized (mLock) {
                 // If we are still the last one to be scheduled, remove ourselves.
-                if (TestProtocol.sDebugTracing) {
-                    Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                            "LauncherModel.close " + mLoaderTask + ", " + mTask);
-                }
                 if (mLoaderTask == mTask) {
                     mLoaderTask = null;
                 }
@@ -573,9 +545,7 @@
     public void updateAndBindWorkspaceItem(WorkspaceItemInfo si, ShortcutInfo info) {
         updateAndBindWorkspaceItem(() -> {
             si.updateFromDeepShortcutInfo(info, mApp.getContext());
-            LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
-            si.bitmap = li.createShortcutIcon(info);
-            li.recycle();
+            mApp.getIconCache().getShortcutIcon(si, info);
             return si;
         });
     }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index b0ab35c..697048a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -118,7 +119,7 @@
 
     @Override
     public boolean onCreate() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             Log.d(TAG, "Launcher process started");
         }
 
@@ -157,6 +158,17 @@
         }
     }
 
+    private synchronized boolean updateCurrentOpenHelper() {
+        final InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
+        if (TextUtils.equals(idp.dbFile, mOpenHelper.getDatabaseName())) {
+            return false;
+        }
+
+        mOpenHelper.close();
+        mOpenHelper = new DatabaseHelper(getContext());
+        return true;
+    }
+
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
@@ -210,7 +222,7 @@
         addModifiedTime(initialValues);
         final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
         if (rowId < 0) return null;
-        mOpenHelper.onAddOrDeleteOp(db);
+        onAddOrDeleteOp(db);
 
         uri = ContentUris.withAppendedId(uri, rowId);
         reloadLauncherIfExternal();
@@ -268,7 +280,7 @@
                     return 0;
                 }
             }
-            mOpenHelper.onAddOrDeleteOp(db);
+            onAddOrDeleteOp(db);
             t.commit();
         }
 
@@ -294,7 +306,7 @@
                         results[i].count != null && results[i].count > 0;
             }
             if (isAddOrDelete) {
-                mOpenHelper.onAddOrDeleteOp(t.getDb());
+                onAddOrDeleteOp(t.getDb());
             }
 
             t.commit();
@@ -316,7 +328,7 @@
         }
         int count = db.delete(args.table, args.where, args.args);
         if (count > 0) {
-            mOpenHelper.onAddOrDeleteOp(db);
+            onAddOrDeleteOp(db);
             reloadLauncherIfExternal();
         }
         return count;
@@ -360,12 +372,14 @@
             }
             case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
                 Bundle result = new Bundle();
-                result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewItemId());
+                result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
+                        mOpenHelper.generateNewItemId());
                 return result;
             }
             case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
                 Bundle result = new Bundle();
-                result.putInt(LauncherSettings.Settings.EXTRA_VALUE, mOpenHelper.generateNewScreenId());
+                result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
+                        mOpenHelper.generateNewScreenId());
                 return result;
             }
             case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
@@ -387,8 +401,12 @@
                 return result;
             }
             case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
-                mOpenHelper.mBackupTableExists =
-                        tableExists(mOpenHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+                // TODO(pinyaoting): Update the behavior here.
+                if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
+                    mOpenHelper.mBackupTableExists =
+                            tableExists(mOpenHelper.getReadableDatabase(),
+                                    Favorites.BACKUP_TABLE_NAME);
+                }
                 return null;
             }
             case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: {
@@ -399,10 +417,26 @@
                         TOKEN_RESTORE_BACKUP_TABLE, RESTORE_BACKUP_TABLE_DELAY);
                 return null;
             }
+            case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
+                if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+                    Bundle result = new Bundle();
+                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+                            updateCurrentOpenHelper());
+                    return result;
+                }
+            }
         }
         return null;
     }
 
+    private void onAddOrDeleteOp(SQLiteDatabase db) {
+        if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
+            // TODO(pingyaoting): Implement the behavior here.
+        } else {
+            mOpenHelper.onAddOrDeleteOp(db);
+        }
+    }
+
     /**
      * Deletes any empty folder from the DB.
      * @return Ids of deleted folders.
@@ -551,14 +585,16 @@
     /**
      * The class is subclassed in tests to create an in-memory db.
      */
-    public static class DatabaseHelper extends NoLocaleSQLiteHelper implements LayoutParserCallback {
+    public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
+            LayoutParserCallback {
         private final Context mContext;
         private int mMaxItemId = -1;
         private int mMaxScreenId = -1;
         private boolean mBackupTableExists;
 
         DatabaseHelper(Context context) {
-            this(context, LauncherFiles.LAUNCHER_DB);
+            this(context, MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
+                    context).dbFile : LauncherFiles.LAUNCHER_DB);
             // Table creation sometimes fails silently, which leads to a crash loop.
             // This way, we will try to create a table every time after crash, so the device
             // would eventually be able to recover.
@@ -567,7 +603,10 @@
                 // This operation is a no-op if the table already exists.
                 addFavoritesTable(getWritableDatabase(), true);
             }
-            mBackupTableExists = tableExists(getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
+            if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
+                mBackupTableExists = tableExists(getReadableDatabase(),
+                        Favorites.BACKUP_TABLE_NAME);
+            }
 
             initIds();
         }
@@ -575,8 +614,8 @@
         /**
          * Constructor used in tests and for restore.
          */
-        public DatabaseHelper(Context context, String tableName) {
-            super(context, tableName, SCHEMA_VERSION);
+        public DatabaseHelper(Context context, String dbName) {
+            super(context, dbName, SCHEMA_VERSION);
             mContext = context;
         }
 
@@ -606,7 +645,7 @@
         }
 
         protected void onAddOrDeleteOp(SQLiteDatabase db) {
-            if (mBackupTableExists) {
+            if (!MULTI_DB_GRID_MIRATION_ALGO.get() && mBackupTableExists) {
                 dropTable(db, Favorites.BACKUP_TABLE_NAME);
                 mBackupTableExists = false;
             }
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 4c5c61c..49831f6 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -302,6 +302,8 @@
 
         public static final String METHOD_RESTORE_BACKUP_TABLE = "restore_backup_table";
 
+        public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
+
         public static final String EXTRA_VALUE = "value";
 
         public static Bundle call(ContentResolver cr, String method) {
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 7e06099..16be391 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -41,8 +41,10 @@
 import static com.android.launcher3.testing.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
 
+import android.view.View;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
@@ -337,8 +339,13 @@
                 hotseat.setScaleX(0.92f);
                 hotseat.setScaleY(0.92f);
                 if (ENABLE_OVERVIEW_ACTIONS.get()) {
-                    launcher.getAppsView().setScaleX(0.92f);
-                    launcher.getAppsView().setScaleY(0.92f);
+                    AllAppsContainerView qsbContainer = launcher.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 (this == NORMAL && fromState == OVERVIEW_PEEK) {
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index ae4eae9..4c6e1f3 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -1502,7 +1502,7 @@
             return false;
         }
 
-        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (FeatureFlags.IS_STUDIO_BUILD) {
             duration *= Settings.Global.getFloat(getContext().getContentResolver(),
                     Settings.Global.WINDOW_ANIMATION_SCALE, 1);
         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7f443b6..e0e4cc0 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -584,9 +584,8 @@
                 return new FixedSizeEmptyDrawable(iconSize);
             }
             ShortcutInfo si = (ShortcutInfo) obj;
-            LauncherIcons li = LauncherIcons.obtain(appState.getContext());
-            Bitmap badge = li.getShortcutInfoBadge(si, appState.getIconCache()).bitmap.icon;
-            li.recycle();
+            Bitmap badge = LauncherAppState.getInstance(appState.getContext())
+                    .getIconCache().getShortcutInfoBadge(si).icon;
             float badgeSize = LauncherIcons.getBadgeSizeForIconSize(iconSize);
             float insetFraction = (iconSize - badgeSize) / iconSize;
             return new InsetDrawable(new FastBitmapDrawable(badge),
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index b725002..a8f492f 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1702,8 +1702,8 @@
             float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
             target.removeView(v);
 
-            FolderIcon fi = mLauncher.addFolder(target, sourceInfo, container, screenId,
-                    targetCell[0], targetCell[1]);
+            FolderIcon fi = mLauncher.addFolder(target, container, screenId, targetCell[0],
+                    targetCell[1]);
             destInfo.cellX = -1;
             destInfo.cellY = -1;
             sourceInfo.cellX = -1;
@@ -1722,6 +1722,7 @@
                 fi.addItem(destInfo);
                 fi.addItem(sourceInfo);
             }
+            mLauncher.folderCreatedFromItem(fi.getFolder(), destInfo);
             return true;
         }
         return false;
@@ -1857,7 +1858,7 @@
                         CellLayout parentCell = getParentCellLayoutForView(cell);
                         if (parentCell != null) {
                             parentCell.removeView(cell);
-                        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                        } else if (FeatureFlags.IS_STUDIO_BUILD) {
                             throw new NullPointerException("mDragInfo.cell has null parent");
                         }
                         addInScreen(cell, container, screenId, mTargetCell[0], mTargetCell[1],
@@ -2152,7 +2153,7 @@
 
         ItemInfo item = d.dragInfo;
         if (item == null) {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new NullPointerException("DragObject has null info");
             }
             return;
@@ -2774,7 +2775,7 @@
                     mDragInfo.container, mDragInfo.screenId);
             if (cellLayout != null) {
                 cellLayout.onDropChild(mDragInfo.cell);
-            } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            } else if (FeatureFlags.IS_STUDIO_BUILD) {
                 throw new RuntimeException("Invalid state: cellLayout == null in "
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
@@ -2793,7 +2794,7 @@
         CellLayout parentCell = getParentCellLayoutForView(v);
         if (parentCell != null) {
             parentCell.removeView(v);
-        } else if (FeatureFlags.IS_DOGFOOD_BUILD) {
+        } else if (FeatureFlags.IS_STUDIO_BUILD) {
             // When an app is uninstalled using the drop target, we wait until resume to remove
             // the icon. We also remove all the corresponding items from the workspace at
             // {@link Launcher#bindComponentsRemoved}. That call can come before or after
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 56bd1b6..4e5b031 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -153,6 +153,11 @@
         return mMultiValueAlpha.getProperty(index);
     }
 
+    public WorkFooterContainer getWorkFooterContainer() {
+        return mWorkFooterContainer;
+    }
+
+
     @Override
     protected void setDampedScrollShift(float shift) {
         // Bound the shift amount to avoid content from drawing on top (Y-val) of the QSB.
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 1bde138..93bdac9 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,7 @@
 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.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -185,7 +185,7 @@
     }
 
     public Animator createSpringAnimation(float... progressValues) {
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (UNSTABLE_SPRINGS.get()) {
             return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
                     SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
         }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index b1a2c33..6413044 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -22,6 +22,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.uioverrides.DeviceFlag;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -42,7 +43,10 @@
         return Utilities.IS_DEBUG_DEVICE && Utilities.isDevelopersOptionsEnabled(context);
     }
 
-    public static final boolean IS_DOGFOOD_BUILD = BuildConfig.DEBUG;
+    /**
+     * True when the build has come from Android Studio and is being used for local debugging.
+     */
+    public static final boolean IS_STUDIO_BUILD = BuildConfig.DEBUG;
 
     /**
      * Enable moving the QSB on the 0th screen of the workspace. This is not a configuration feature
@@ -90,13 +94,13 @@
             "FAKE_LANDSCAPE_UI", false, "Rotate launcher UI instead of using transposed layout");
 
     public static final BooleanFlag FOLDER_NAME_SUGGEST = getDebugFlag(
-            "FOLDER_NAME_SUGGEST", false, "Suggests folder names instead of blank text.");
+            "FOLDER_NAME_SUGGEST", true, "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
             "APP_SEARCH_IMPROVEMENTS", false,
             "Adds localized title and keyword search and ranking");
 
-    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
+    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag(
             "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
     public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
@@ -106,12 +110,15 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
+    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
             "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
 
+    public static final BooleanFlag MULTI_DB_GRID_MIRATION_ALGO = getDebugFlag(
+            "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm");
+
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
             "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", false,
             "Show launcher preview in grid picker");
@@ -142,6 +149,15 @@
         }
     }
 
+    public static void dump(PrintWriter pw) {
+        pw.println("FeatureFlags:");
+        synchronized (sDebugFlags) {
+            for (DebugFlag flag : sDebugFlags) {
+                pw.println("  " + flag.key + "=" + flag.get());
+            }
+        }
+    }
+
     public static class BooleanFlag {
 
         public final String key;
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1b7b015..de7bc6d 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -160,8 +160,8 @@
 
         mOptions = options;
         if (mOptions.systemDndStartPoint != null) {
-            mMotionDownX = mOptions.systemDndStartPoint.x;
-            mMotionDownY = mOptions.systemDndStartPoint.y;
+            mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
+            mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
         }
 
         final int registrationX = mMotionDownX - dragLayerX;
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 024c7dd..1d79e01 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -16,9 +16,23 @@
 
 package com.android.launcher3.folder;
 
+import static android.text.TextUtils.isEmpty;
+
+import static androidx.core.util.Preconditions.checkNotNull;
+
+import static com.android.launcher3.FolderInfo.FLAG_MANUAL_FOLDER_NAME;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+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.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -75,20 +89,24 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
-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.userevent.nano.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Action;
+import com.android.launcher3.userevent.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Represents a set of icons chosen by the user or generated by the system.
@@ -186,6 +204,9 @@
     @Thunk int mScrollHintDir = SCROLL_NONE;
     @Thunk int mCurrentScrollDir = SCROLL_NONE;
 
+    private String mPreviousLabel;
+    private boolean mIsPreviousLabelSuggested;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -300,13 +321,13 @@
     public void startEditingFolderName() {
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                if (TextUtils.isEmpty(mFolderName.getText())) {
-                    String[] suggestedNames =
-                            mInfo.suggestedFolderNames.getStringArrayExtra("suggest");
-                    mFolderName.setText(suggestedNames[0]);
-                    mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1,
-                            suggestedNames.length));
-                    mFolderName.setEnteredCompose(false);
+                if (isEmpty(mFolderName.getText())) {
+                    FolderNameInfo[] nameInfos =
+                            (FolderNameInfo[]) mInfo.suggestedFolderNames.getParcelableArrayExtra(
+                                    FolderInfo.EXTRA_FOLDER_SUGGESTIONS);
+                    if (nameInfos != null) {
+                        showLabelSuggestion(nameInfos, false);
+                    }
                 }
             }
             mFolderName.setHint("");
@@ -324,23 +345,16 @@
         }
 
         mInfo.title = newTitle;
-        mInfo.setOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+        mInfo.setOption(FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
                 mLauncher.getModelWriter());
         mFolderIcon.onTitleChanged(newTitle);
         mLauncher.getModelWriter().updateItemInDatabase(mInfo);
 
-        if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mFolderName.setText(mInfo.title);
-            // TODO: depending on whether the title was manually edited or automatically
-            // suggested, apply different hint.
-            mFolderName.setHint("");
+        if (TextUtils.isEmpty(mInfo.title)) {
+            mFolderName.setHint(R.string.folder_hint_text);
+            mFolderName.setText("");
         } else {
-            if (TextUtils.isEmpty(mInfo.title)) {
-                mFolderName.setHint(R.string.folder_hint_text);
-                mFolderName.setText("");
-            } else {
-                mFolderName.setHint(null);
-            }
+            mFolderName.setHint(null);
         }
 
         sendCustomAccessibilityEvent(
@@ -423,8 +437,10 @@
         mItemsInvalidated = true;
         mInfo.addListener(this);
 
-        if (!TextUtils.isEmpty(mInfo.title)) {
+        if (!isEmpty(mInfo.title)) {
             mFolderName.setText(mInfo.title);
+            mPreviousLabel = mInfo.title.toString();
+            mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
             mFolderName.setHint(null);
         } else {
             mFolderName.setText("");
@@ -443,27 +459,54 @@
     }
 
     /**
-     * Show suggested folder title.
+     * Show suggested folder title in FolderEditText, push InputMethodManager suggestions and save
+     * the suggestedFolderNames.
      */
-    public void showSuggestedTitle(String[] suggestName) {
+    public void showSuggestedTitle(FolderNameInfo[] nameInfos) {
         if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-            mInfo.suggestedFolderNames = new Intent().putExtra("suggest", suggestName);
-            if (TextUtils.isEmpty(mFolderName.getText().toString())
-                    && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
-                if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
-                    mFolderName.setHint("");
-                    mFolderName.setText(suggestName[0]);
-                    mInfo.title = suggestName[0];
-                    animateOpen(mInfo.contents, 0, true);
-                    mFolderName.showKeyboard();
-                    mFolderName.displayCompletions(
-                            Arrays.asList(suggestName).subList(1, suggestName.length));
-                }
+            mInfo.suggestedFolderNames = new Intent().putExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS,
+                    nameInfos);
+            if (isEmpty(mFolderName.getText().toString())
+                    && !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME)) {
+                showLabelSuggestion(nameInfos, true);
             }
         }
     }
 
     /**
+     * Show suggested folder title in FolderEditText if the first suggestion is non-empty, push
+     * InputMethodManager suggestions.
+     */
+    private void showLabelSuggestion(FolderNameInfo[] nameInfos, boolean animate) {
+        if (nameInfos == null) {
+            return;
+        }
+        // Open the Folder and Keyboard when the first or second suggestion is valid non-empty
+        // string.
+        boolean shouldOpen = nameInfos.length > 0 && nameInfos[0] != null && !isEmpty(
+                nameInfos[0].getLabel())
+                || nameInfos.length > 1 && nameInfos[1] != null && !isEmpty(
+                nameInfos[1].getLabel());
+        CharSequence firstLabel = nameInfos[0].getLabel();
+
+        if (shouldOpen) {
+            if (!isEmpty(firstLabel)) {
+                mFolderName.setHint("");
+                mFolderName.setText(firstLabel);
+            }
+            if (animate) {
+                animateOpen(mInfo.contents, 0, true);
+            }
+            mFolderName.showKeyboard();
+            mFolderName.displayCompletions(
+                    asList(nameInfos).subList(1, nameInfos.length).stream()
+                            .filter(Objects::nonNull)
+                            .map(s -> s.getLabel().toString())
+                            .collect(Collectors.toList()));
+        }
+    }
+
+    /**
      * Creates a new UserFolder, inflated from R.layout.user_folder.
      *
      * @param launcher The main activity.
@@ -580,7 +623,7 @@
             dragLayer.addView(this);
             mDragController.addDropTarget(this);
         } else {
-            if (FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (FeatureFlags.IS_STUDIO_BUILD) {
                 Log.e(TAG, "Opening folder (" + this + ") which already has a parent:"
                         + getParent());
             }
@@ -608,9 +651,9 @@
 
                 if (!skipUserEventLog) {
                     mLauncher.getUserEventDispatcher().logActionOnItem(
-                        Touch.TAP,
-                        Direction.NONE,
-                        ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+                            LauncherLogProto.Action.Touch.TAP,
+                            LauncherLogProto.Action.Direction.NONE,
+                            LauncherLogProto.ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
                 }
 
 
@@ -1109,13 +1152,14 @@
                 int itemCount = getItemCount();
                 if (itemCount <= 1) {
                     View newIcon = null;
+                    WorkspaceItemInfo finalItem = null;
 
                     if (itemCount == 1) {
                         // Move the item from the folder to the workspace, in the position of the
                         // folder
                         CellLayout cellLayout = mLauncher.getCellLayout(mInfo.container,
                                 mInfo.screenId);
-                        WorkspaceItemInfo finalItem = mInfo.contents.remove(0);
+                        finalItem =  mInfo.contents.remove(0);
                         newIcon = mLauncher.createShortcut(cellLayout, finalItem);
                         mLauncher.getModelWriter().addOrMoveItemInDatabase(finalItem,
                                 mInfo.container, mInfo.screenId, mInfo.cellX, mInfo.cellY);
@@ -1136,6 +1180,9 @@
                         // Focus the newly created child
                         newIcon.requestFocus();
                     }
+                    if (finalItem != null) {
+                        mLauncher.folderConvertedToItem(mFolderIcon.getFolder(), finalItem);
+                    }
                 }
             }
         };
@@ -1388,6 +1435,7 @@
             if (hasFocus) {
                 startEditingFolderName();
             } else {
+                logEditFolderLabel();
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1401,11 +1449,12 @@
     }
 
     @Override
-    public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
+    public void fillInLogContainerData(View v, ItemInfo info, LauncherLogProto.Target target,
+            LauncherLogProto.Target targetParent) {
         target.gridX = info.cellX;
         target.gridY = info.cellY;
         target.pageIndex = mContent.getCurrentPage();
-        targetParent.containerType = ContainerType.FOLDER;
+        targetParent.containerType = LauncherLogProto.ContainerType.FOLDER;
     }
 
     private class OnScrollHintListener implements OnAlarmListener {
@@ -1503,7 +1552,7 @@
 
     @Override
     public int getLogContainerType() {
-        return ContainerType.FOLDER;
+        return LauncherLogProto.ContainerType.FOLDER;
     }
 
     /**
@@ -1538,7 +1587,7 @@
                     }
                 } else {
                     mLauncher.getUserEventDispatcher().logActionTapOutside(
-                            LoggerUtils.newContainerTarget(ContainerType.FOLDER));
+                            LoggerUtils.newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
                     close(true);
                     return true;
                 }
@@ -1568,4 +1617,113 @@
             super.draw(canvas);
         }
     }
+
+    private void logEditFolderLabel() {
+        LauncherEvent launcherEvent = LauncherEvent.newBuilder()
+                .setAction(Action.newBuilder().setType(Action.Type.SOFT_KEYBOARD))
+                .addSrcTarget(newEditTextTargetBuilder()
+                        .setFromFolderLabelState(getFromFolderLabelState())
+                        .setToFolderLabelState(getToFolderLabelState()))
+                .addSrcTarget(newFolderTargetBuilder())
+                .addSrcTarget(newParentContainerTarget())
+                .build();
+        mLauncher.getUserEventDispatcher().logLauncherEvent(launcherEvent);
+        mPreviousLabel = mFolderName.getText().toString();
+        mIsPreviousLabelSuggested = !mInfo.hasOption(FLAG_MANUAL_FOLDER_NAME);
+    }
+
+    private Target.FromFolderLabelState getFromFolderLabelState() {
+        return mPreviousLabel == null
+                ? FROM_FOLDER_LABEL_STATE_UNSPECIFIED
+                : mPreviousLabel.isEmpty()
+                ? FROM_EMPTY
+                : mIsPreviousLabelSuggested
+                ? FROM_SUGGESTED
+                : FROM_CUSTOM;
+    }
+
+    private Target.ToFolderLabelState getToFolderLabelState() {
+        String newLabel =
+                checkNotNull(mFolderName.getText().toString(),
+                        "Expected valid folder label, but found null");
+
+        Optional<String[]> suggestedLabels = Optional.ofNullable(
+                (FolderNameInfo[]) mInfo.suggestedFolderNames
+                        .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+                .map(folderNameInfoArray ->
+                        stream(folderNameInfoArray)
+                                .filter(Objects::nonNull)
+                                .map(FolderNameInfo::getLabel)
+                                .map(CharSequence::toString)
+                                .toArray(String[]::new));
+
+
+        int accepted_suggestion_index = suggestedLabels
+                .map(folderNameInfoArray ->
+                        IntStream.range(0, folderNameInfoArray.length)
+                                .filter(index -> newLabel.equalsIgnoreCase(
+                                        folderNameInfoArray[index]))
+                                .findFirst()
+                                .orElse(-1)
+                ).orElse(-1);
+
+        boolean hasValidPrimary = suggestedLabels
+                .map(labels -> labels.length > 0 && !isEmpty(labels[0]))
+                .orElse(false);
+        String primarySuffix = hasValidPrimary
+                ? "_WITH_VALID_PRIMARY"
+                : "_WITH_EMPTY_PRIMARY";
+
+        boolean isEmptySuggestions = suggestedLabels
+                .map(labels -> stream(labels).allMatch(TextUtils::isEmpty))
+                .orElse(true);
+        boolean isSuggestionsEnabled = FeatureFlags.FOLDER_NAME_SUGGEST.get();
+        String suggestionsSuffix =  isSuggestionsEnabled
+                ? isEmptySuggestions
+                    ? "_WITH_EMPTY_SUGGESTIONS"
+                    : "_WITH_VALID_SUGGESTIONS"
+                : "_WITH_SUGGESTIONS_DISABLED";
+
+        return newLabel.equals(mPreviousLabel)
+                ? Target.ToFolderLabelState.UNCHANGED
+                : newLabel.isEmpty()
+                    ? Target.ToFolderLabelState.valueOf("TO_EMPTY" + suggestionsSuffix)
+                    : accepted_suggestion_index >= 0
+                        ? Target.ToFolderLabelState.valueOf("TO_SUGGESTION"
+                            + accepted_suggestion_index
+                            + primarySuffix)
+                        : Target.ToFolderLabelState.valueOf("TO_CUSTOM" + suggestionsSuffix);
+    }
+
+
+    private Target.Builder newEditTextTargetBuilder() {
+        return Target.newBuilder().setType(Target.Type.ITEM).setItemType(ItemType.EDITTEXT);
+    }
+
+    private Target.Builder newFolderTargetBuilder() {
+        return Target.newBuilder()
+                .setType(Target.Type.CONTAINER)
+                .setContainerType(ContainerType.FOLDER)
+                .setPageIndex(mInfo.screenId)
+                .setGridX(mInfo.cellX)
+                .setGridY(mInfo.cellY)
+                .setCardinality(mInfo.contents.size());
+    }
+
+    private Target.Builder newParentContainerTarget() {
+        Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+
+        switch (mInfo.container) {
+            case CONTAINER_HOTSEAT:
+                return builder.setContainerType(ContainerType.HOTSEAT);
+            case CONTAINER_DESKTOP:
+                return builder.setContainerType(ContainerType.WORKSPACE);
+            default:
+                throw new AssertionError(String
+                        .format("Expected container to be either %s or %s but found %s.",
+                                CONTAINER_HOTSEAT,
+                                CONTAINER_DESKTOP,
+                                mInfo.container));
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 6a47b98..ab1ff10 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -393,15 +393,16 @@
             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
             final int finalIndex = index;
 
-            String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
+            FolderNameInfo[] nameInfos =
+                    new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 Executors.UI_HELPER_EXECUTOR.post(() -> {
                     d.folderNameProvider.getSuggestedFolderName(
-                            getContext(), mInfo.contents, suggestedNameOut);
-                    showFinalView(finalIndex, item, suggestedNameOut);
+                            getContext(), mInfo.contents, nameInfos);
+                    showFinalView(finalIndex, item, nameInfos);
                 });
             } else {
-                showFinalView(finalIndex, item, suggestedNameOut);
+                showFinalView(finalIndex, item, nameInfos);
             }
         } else {
             addItem(item);
@@ -409,12 +410,12 @@
     }
 
     private void showFinalView(int finalIndex, final WorkspaceItemInfo item,
-            String[] suggestedNameOut) {
+            FolderNameInfo[] nameInfos) {
         postDelayed(() -> {
             mPreviewItemManager.hidePreviewItem(finalIndex, false);
             mFolder.showItem(item);
             invalidate();
-            mFolder.showSuggestedTitle(suggestedNameOut);
+            mFolder.showSuggestedTitle(nameInfos);
         }, DROP_IN_ANIMATION_DURATION);
     }
 
diff --git a/src/com/android/launcher3/folder/FolderNameInfo.java b/src/com/android/launcher3/folder/FolderNameInfo.java
new file mode 100644
index 0000000..eb9da90
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameInfo.java
@@ -0,0 +1,83 @@
+/*
+ * 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.folder;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Information about a single label suggestions of the Folder.
+ */
+
+public final class FolderNameInfo implements Parcelable {
+    private final double mScore;
+    private final CharSequence mLabel;
+
+    /**
+     * Create a simple completion with label.
+     *
+     * @param label The text that should be inserted into the editor and pushed to
+     *              InputMethodManager suggestions.
+     * @param score The score for the label between 0.0 and 1.0.
+     */
+    public FolderNameInfo(CharSequence label, double score) {
+        mScore = score;
+        mLabel = label;
+    }
+
+    private FolderNameInfo(Parcel source) {
+        mScore = source.readDouble();
+        mLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+    }
+
+    public CharSequence getLabel() {
+        return mLabel;
+    }
+
+    /**
+     * Used to package this object into a {@link Parcel}.
+     *
+     * @param dest  The {@link Parcel} to be written.
+     * @param flags The flags used for parceling.
+     */
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeDouble(mScore);
+        TextUtils.writeToParcel(mLabel, dest, flags);
+    }
+
+    /**
+     * Used to make this class parcelable.
+     */
+    @NonNull
+    public static final Parcelable.Creator<FolderNameInfo> CREATOR =
+            new Parcelable.Creator<FolderNameInfo>() {
+                public FolderNameInfo createFromParcel(Parcel source) {
+                    return new FolderNameInfo(source);
+                }
+
+                public FolderNameInfo[] newArray(int size) {
+                    return new FolderNameInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 957e636..184dbb9 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -21,15 +21,21 @@
 import android.util.Log;
 
 import com.android.launcher3.AppInfo;
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
@@ -49,6 +55,8 @@
      * name edit box can also be used to provide suggestion.
      */
     public static final int SUGGEST_MAX = 4;
+    protected IntSparseArrayMap<FolderInfo> mFolderInfos;
+    protected List<AppInfo> mAppInfos;
 
     /**
      * Retrieve instance of this object that can be overridden in runtime based on the build
@@ -57,78 +65,113 @@
     public static FolderNameProvider newInstance(Context context) {
         FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
                 context.getApplicationContext(), R.string.folder_name_provider_class);
+        fnp.load(context);
+
         return fnp;
     }
 
-    public CharSequence getSuggestedFolderName(Context context,
-            ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
+    public static FolderNameProvider newInstance(Context context, List<AppInfo> appInfos,
+            IntSparseArrayMap<FolderInfo> folderInfos) {
+        FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
+                context.getApplicationContext(), R.string.folder_name_provider_class);
+        fnp.load(appInfos, folderInfos);
+
+        return fnp;
+    }
+
+    private void load(Context context) {
+        LauncherAppState.getInstance(context).getModel().enqueueModelUpdateTask(
+                new FolderNameWorker());
+    }
+
+    private void load(List<AppInfo> appInfos, IntSparseArrayMap<FolderInfo> folderInfos) {
+        mAppInfos = appInfos;
+        mFolderInfos = folderInfos;
+    }
+
+    /**
+     * Generate and rank the suggested Folder names.
+     */
+    public void getSuggestedFolderName(Context context,
+            ArrayList<WorkspaceItemInfo> workspaceItemInfos,
+            FolderNameInfo[] nameInfos) {
 
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
         List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.user))
+                .filter(distinctByKey(p -> p.user))
                 .collect(Collectors.toList());
 
         if (distinctItemInfos.size() == 1
                 && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
             // Place it as last viable suggestion
-            setAsLastSuggestion(candidates,
+            setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
         distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p-> p.getTargetComponent() != null
+                .filter(distinctByKey(p -> p.getTargetComponent() != null
                         ? p.getTargetComponent().getPackageName() : ""))
                 .collect(Collectors.toList());
 
         if (distinctItemInfos.size() == 1) {
-            Optional<AppInfo> info = LauncherAppState.getInstance(context).getModel()
-                    .getAppInfoByPackageName(distinctItemInfos.get(0).getTargetComponent()
-                            .getPackageName());
+            Optional<AppInfo> info = getAppInfoByPackageName(
+                    distinctItemInfos.get(0).getTargetComponent().getPackageName());
             // Place it as first viable suggestion and shift everything else
-            info.ifPresent(i -> setAsFirstSuggestion(candidates, i.title.toString()));
+            info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
         if (DEBUG) {
-            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(candidates));
+            Log.d(TAG, "getSuggestedFolderName:" + Arrays.toString(nameInfos));
         }
-        return candidates[0];
     }
 
-    private void setAsFirstSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private Optional<AppInfo> getAppInfoByPackageName(String packageName) {
+        if (mAppInfos == null || mAppInfos.isEmpty()) {
+            return Optional.empty();
+        }
+        return mAppInfos.stream()
+                .filter(info -> info.componentName.getPackageName().equals(packageName))
+                .findAny();
+    }
+
+    private void setAsFirstSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
-        for (int i = candidatesOut.length - 1; i > 0; i--) {
-            if (!TextUtils.isEmpty(candidatesOut[i - 1])) {
-                candidatesOut[i] = candidatesOut[i - 1];
+        for (int i = nameInfos.length - 1; i > 0; i--) {
+            if (nameInfos[i - 1] != null && !TextUtils.isEmpty(nameInfos[i - 1].getLabel())) {
+                nameInfos[i] = nameInfos[i - 1];
             }
         }
-        candidatesOut[0] = candidate;
+        nameInfos[0] = new FolderNameInfo(label, 1.0);
     }
 
-    private void setAsLastSuggestion(CharSequence[] candidatesOut, CharSequence candidate) {
-        if (contains(candidatesOut, candidate)) {
+    private void setAsLastSuggestion(FolderNameInfo[] nameInfos, CharSequence label) {
+        if (nameInfos.length == 0 || contains(nameInfos, label)) {
             return;
         }
 
-        for (int i = 0; i < candidate.length(); i++) {
-            if (TextUtils.isEmpty(candidatesOut[i])) {
-                candidatesOut[i] = candidate;
+        for (int i = 0; i < nameInfos.length; i++) {
+            if (nameInfos[i] == null || TextUtils.isEmpty(nameInfos[i].getLabel())) {
+                nameInfos[i] = new FolderNameInfo(label, 1.0);
                 return;
             }
         }
-        candidatesOut[candidate.length() - 1] = candidate;
+        // Overwrite the last suggestion.
+        int lastIndex = nameInfos.length - 1;
+        nameInfos[lastIndex] = new FolderNameInfo(label, 1.0);
     }
 
-    private boolean contains(CharSequence[] list, CharSequence key) {
-        return Arrays.asList(list).stream()
-                .filter(s -> s != null)
-                .anyMatch(s -> s.toString().equalsIgnoreCase(key.toString()));
+    private boolean contains(FolderNameInfo[] nameInfos, CharSequence label) {
+        return Arrays.stream(nameInfos)
+                .filter(Objects::nonNull)
+                .anyMatch(nameInfo -> nameInfo.getLabel().toString().equalsIgnoreCase(
+                        label.toString()));
     }
 
     // This method can be moved to some Utility class location.
@@ -136,4 +179,13 @@
         Map<Object, Boolean> map = new ConcurrentHashMap<>();
         return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
     }
+
+    private class FolderNameWorker extends BaseModelUpdateTask {
+        @Override
+        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            mFolderInfos = dataModel.folders.clone();
+            mAppInfos = Arrays.asList(apps.copyData());
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index f579451..94d30f6 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -152,7 +152,7 @@
     }
 
     public final void generateDragOutline(Bitmap preview) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && mOutlineGeneratorCallback != null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && mOutlineGeneratorCallback != null) {
             throw new RuntimeException("Drag outline generated twice");
         }
 
diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java
index f7ee5f9..372c591 100644
--- a/src/com/android/launcher3/icons/ComponentWithLabel.java
+++ b/src/com/android/launcher3/icons/ComponentWithLabel.java
@@ -31,7 +31,7 @@
     CharSequence getLabel(PackageManager pm);
 
 
-    class ComponentCachingLogic implements CachingLogic<ComponentWithLabel> {
+    class ComponentCachingLogic<T extends ComponentWithLabel> implements CachingLogic<T> {
 
         private final PackageManager mPackageManager;
         private final boolean mAddToMemCache;
@@ -42,22 +42,22 @@
         }
 
         @Override
-        public ComponentName getComponent(ComponentWithLabel object) {
+        public ComponentName getComponent(T object) {
             return object.getComponent();
         }
 
         @Override
-        public UserHandle getUser(ComponentWithLabel object) {
+        public UserHandle getUser(T object) {
             return object.getUser();
         }
 
         @Override
-        public CharSequence getLabel(ComponentWithLabel object) {
+        public CharSequence getLabel(T object) {
             return object.getLabel(mPackageManager);
         }
 
         @Override
-        public BitmapInfo loadIcon(Context context, ComponentWithLabel object) {
+        public BitmapInfo loadIcon(Context context, T object) {
             return BitmapInfo.LOW_RES_INFO;
         }
 
diff --git a/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
new file mode 100644
index 0000000..248a57d
--- /dev/null
+++ b/src/com/android/launcher3/icons/ComponentWithLabelAndIcon.java
@@ -0,0 +1,54 @@
+/*
+ * 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.icons;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+
+/**
+ * Extension of ComponentWithLabel to also support loading icons
+ */
+public interface ComponentWithLabelAndIcon extends ComponentWithLabel {
+
+    /**
+     * Provide an icon for this object
+     */
+    Drawable getFullResIcon(IconCache cache);
+
+    class ComponentWithIconCachingLogic extends ComponentCachingLogic<ComponentWithLabelAndIcon> {
+
+        public ComponentWithIconCachingLogic(Context context, boolean addToMemCache) {
+            super(context, addToMemCache);
+        }
+
+        @NonNull
+        @Override
+        public BitmapInfo loadIcon(Context context, ComponentWithLabelAndIcon object) {
+            Drawable d = object.getFullResIcon(LauncherAppState.getInstance(context)
+                    .getIconCache());
+            if (d == null) {
+                return super.loadIcon(context, object);
+            }
+            try (LauncherIcons li = LauncherIcons.obtain(context)) {
+                return li.createBadgedIconBitmap(d, object.getUser(), 0);
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 69b8125..804acc3 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
@@ -56,6 +57,7 @@
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 
+import java.util.function.Predicate;
 import java.util.function.Supplier;
 
 /**
@@ -65,6 +67,9 @@
 
     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 CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
     private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
@@ -180,6 +185,77 @@
     }
 
     /**
+     * Fill in {@param info} with the icon for {@param si}
+     */
+    public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+        getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck);
+    }
+
+    /**
+     * Fill in {@param info} with an unbadged icon for {@param si}
+     */
+    public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
+        getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck);
+    }
+
+    /**
+     * Fill in {@param info} with the icon and label for {@param si}. If the icon is not
+     * available, and fallback check returns true, it keeps the old icon.
+     */
+    public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
+            @NonNull Predicate<T> fallbackIconCheck) {
+        getShortcutIcon(info, si, true /* use badged */, fallbackIconCheck);
+    }
+
+    private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
+            boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) {
+        BitmapInfo bitmapInfo;
+        if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
+            bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
+                    () -> si, mShortcutCachingLogic, false, false).bitmap;
+        } else {
+            // If caching is disabled, load the full icon
+            bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
+        }
+        if (bitmapInfo.isNullOrLowRes()) {
+            bitmapInfo = getDefaultIcon(si.getUserHandle());
+        }
+
+        if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
+            return;
+        }
+        info.bitmap = bitmapInfo;
+        if (useBadged) {
+            BitmapInfo badgeInfo = getShortcutInfoBadge(si);
+            try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+                info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo);
+            }
+        }
+    }
+
+    /**
+     * Returns the badging info for the shortcut
+     */
+    public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) {
+        ComponentName cn = shortcutInfo.getActivity();
+        if (cn != null) {
+            // Get the app info for the source activity.
+            AppInfo appInfo = new AppInfo();
+            appInfo.user = shortcutInfo.getUserHandle();
+            appInfo.componentName = cn;
+            appInfo.intent = new Intent(Intent.ACTION_MAIN)
+                    .addCategory(Intent.CATEGORY_LAUNCHER)
+                    .setComponent(cn);
+            getTitleAndIcon(appInfo, false);
+            return appInfo.bitmap;
+        } else {
+            PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
+            getTitleAndIconForApp(pkgInfo, false);
+            return pkgInfo.bitmap;
+        }
+    }
+
+    /**
      * Fill in info with the icon and label for deep shortcut.
      */
     public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index e67d244..cbd7c53 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -16,28 +16,11 @@
 
 package com.android.launcher3.icons;
 
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Bitmap;
-import android.graphics.drawable.Drawable;
-import android.os.Process;
 
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.AppInfo;
-import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.model.PackageItemInfo;
-import com.android.launcher3.util.Themes;
-
-import java.util.function.Supplier;
 
 /**
  * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@@ -111,114 +94,4 @@
     public void close() {
         recycle();
     }
-
-    // below methods should also migrate to BaseIconFactory
-    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo) {
-        return createShortcutIcon(shortcutInfo, true /* badged */);
-    }
-
-    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged) {
-        return createShortcutIcon(shortcutInfo, badged, null);
-    }
-
-    public BitmapInfo createShortcutIcon(ShortcutInfo shortcutInfo, boolean badged,
-            @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
-        if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-            return createShortcutIconCached(shortcutInfo, badged, true, fallbackIconProvider);
-        } else {
-            return createShortcutIconLegacy(shortcutInfo, badged, fallbackIconProvider);
-        }
-    }
-
-    public BitmapInfo createShortcutIconLegacy(ShortcutInfo shortcutInfo, boolean badged,
-            @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
-        Drawable unbadgedDrawable = ShortcutCachingLogic.getIcon(
-                mContext, shortcutInfo, mFillResIconDpi);
-        IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-        final Bitmap unbadgedBitmap;
-        if (unbadgedDrawable != null) {
-            unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
-        } else {
-            if (fallbackIconProvider != null) {
-                // Fallback icons are already badged and with appropriate shadow
-                ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
-                if (fullIcon != null && fullIcon.bitmap != null) {
-                    return fullIcon.bitmap;
-                }
-            }
-            unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
-        }
-
-        if (!badged) {
-            return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext));
-        }
-
-        final Bitmap unbadgedfinal = unbadgedBitmap;
-        final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
-
-        Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
-            getShadowGenerator().recreateIcon(unbadgedfinal, c);
-            badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap));
-        });
-        return BitmapInfo.of(icon, badge.bitmap.color);
-    }
-
-    @WorkerThread
-    public BitmapInfo createShortcutIconCached(ShortcutInfo shortcutInfo, boolean badged,
-            boolean useCache, @Nullable Supplier<ItemInfoWithIcon> fallbackIconProvider) {
-        IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
-        final BitmapInfo bitmapInfo;
-        if (useCache) {
-            bitmapInfo = cache.getDeepShortcutTitleAndIcon(shortcutInfo).bitmap;
-        } else {
-            bitmapInfo = new ShortcutCachingLogic().loadIcon(mContext, shortcutInfo);
-        }
-        final Bitmap unbadgedBitmap;
-        if (bitmapInfo.icon != null) {
-            unbadgedBitmap = bitmapInfo.icon;
-        } else {
-            if (fallbackIconProvider != null) {
-                // Fallback icons are already badged and with appropriate shadow
-                ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
-                if (fullIcon != null && fullIcon.bitmap != null) {
-                    return fullIcon.bitmap;
-                }
-            }
-            unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
-        }
-
-        if (!badged) {
-            return BitmapInfo.of(unbadgedBitmap, Themes.getColorAccent(mContext));
-        }
-
-        final Bitmap unbadgedfinal = unbadgedBitmap;
-        final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
-
-        Bitmap icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
-            getShadowGenerator().recreateIcon(unbadgedfinal, c);
-            badgeWithDrawable(c, new FastBitmapDrawable(badge.bitmap));
-        });
-        return BitmapInfo.of(icon, badge.bitmap.color);
-    }
-
-    public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfo shortcutInfo, IconCache cache) {
-        ComponentName cn = shortcutInfo.getActivity();
-        String badgePkg = shortcutInfo.getPackage();
-        boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
-        if (cn != null && !hasBadgePkgSet) {
-            // Get the app info for the source activity.
-            AppInfo appInfo = new AppInfo();
-            appInfo.user = shortcutInfo.getUserHandle();
-            appInfo.componentName = cn;
-            appInfo.intent = new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_LAUNCHER)
-                    .setComponent(cn);
-            cache.getTitleAndIcon(appInfo, false);
-            return appInfo;
-        } else {
-            PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
-            cache.getTitleAndIconForApp(pkgInfo, false);
-            return pkgInfo;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/icons/ShortcutCachingLogic.java b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
index b856dd1..d7eed06 100644
--- a/src/com/android/launcher3/icons/ShortcutCachingLogic.java
+++ b/src/com/android/launcher3/icons/ShortcutCachingLogic.java
@@ -25,6 +25,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -57,6 +58,12 @@
         return info.getShortLabel();
     }
 
+    @Override
+    public CharSequence getDescription(ShortcutInfo object, CharSequence fallback) {
+        CharSequence label = object.getLongLabel();
+        return TextUtils.isEmpty(label) ? fallback : label;
+    }
+
     @NonNull
     @Override
     public BitmapInfo loadIcon(Context context, ShortcutInfo info) {
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 2c972a0..67f07b1 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -42,7 +42,7 @@
     private static Handler sHandler = null;
     private static File sLogsDirectory = null;
 
-    private static final int LOG_DAYS = 2;
+    public static final int LOG_DAYS = 4;
 
     public static void setDir(File logsDir) {
         if (ENABLED) {
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 8289da9..199d13f 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -69,8 +69,7 @@
 public class UserEventDispatcher implements ResourceBasedOverride {
 
     private static final String TAG = "UserEvent";
-    private static final boolean IS_VERBOSE =
-            FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+    private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.USEREVENT);
     private static final String UUID_STORAGE = "uuid";
 
     public static UserEventDispatcher newInstance(Context context,
@@ -372,6 +371,25 @@
         dispatchUserEvent(event, null);
     }
 
+    /**
+     * Logs proto lite version of LauncherEvent object to clearcut.
+     */
+    public void logLauncherEvent(
+                com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
+
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
+        mAppOrTaskLaunch = false;
+        launcherEvent.toBuilder()
+            .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
+            .setElapsedSessionMillis(SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
+        if (!IS_VERBOSE) {
+            return;
+        }
+        Log.d(TAG, launcherEvent.toString());
+    }
+
     public void logDeepShortcutsOpen(View icon) {
         LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(icon);
         if (icon == null || !(icon.getTag() instanceof ItemInfo || provider == null)) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index c24b939..32fce0b 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -273,7 +273,7 @@
             switch (item.itemType) {
                 case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                     folders.remove(item.id);
-                    if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                    if (FeatureFlags.IS_STUDIO_BUILD) {
                         for (ItemInfo info : itemsIdMap) {
                             if (info.container == item.id) {
                                 // We are deleting a folder which still contains items that
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index fc9948e..07a7551 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -74,8 +74,8 @@
     @IntDef({STATE_NOT_FOUND, STATE_RAW, STATE_SANITIZED})
     private @interface BackupState { }
 
-    public GridBackupTable(Context context, SQLiteDatabase db,
-            int hotseatSize, int gridX, int gridY) {
+    public GridBackupTable(Context context, SQLiteDatabase db, int hotseatSize, int gridX,
+            int gridY) {
         mContext = context;
         mDb = db;
 
@@ -105,7 +105,7 @@
         }
         long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
                 Process.myUserHandle());
-        copyTable(BACKUP_TABLE_NAME, Favorites.TABLE_NAME, userSerial);
+        copyTable(mDb, BACKUP_TABLE_NAME, Favorites.TABLE_NAME, userSerial);
         Log.d(TAG, "Backup table found");
         return true;
     }
@@ -118,10 +118,10 @@
     /**
      * Copy valid grid entries from one table to another.
      */
-    private void copyTable(String from, String to, long userSerial) {
-        dropTable(mDb, to);
-        Favorites.addTableToDb(mDb, userSerial, false, to);
-        mDb.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
+    private static void copyTable(SQLiteDatabase db, String from, String to, long userSerial) {
+        dropTable(db, to);
+        Favorites.addTableToDb(db, userSerial, false, to);
+        db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from + " where _id > " + ID_PROPERTY);
     }
 
     private void encodeDBProperties(int options) {
@@ -174,7 +174,7 @@
             // skip restore if dimensions in backup table differs from current setup.
             return false;
         }
-        copyTable(Favorites.BACKUP_TABLE_NAME, Favorites.TABLE_NAME, oldProfileId);
+        copyTable(mDb, Favorites.BACKUP_TABLE_NAME, Favorites.TABLE_NAME, oldProfileId);
         Log.d(TAG, "Backup restored");
         return true;
     }
@@ -183,7 +183,7 @@
      * Performs a backup on the workspace layout.
      */
     public void doBackup(long profileId, int options) {
-        copyTable(Favorites.TABLE_NAME, Favorites.BACKUP_TABLE_NAME, profileId);
+        copyTable(mDb, Favorites.TABLE_NAME, Favorites.BACKUP_TABLE_NAME, profileId);
         encodeDBProperties(options);
     }
 
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
new file mode 100644
index 0000000..63b7191
--- /dev/null
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.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.launcher3.model;
+
+import android.content.Context;
+
+/**
+ * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
+ * result of restoring from a larger device or device density change.
+ */
+public class GridSizeMigrationTaskV2 {
+
+    private GridSizeMigrationTaskV2(Context context) {
+
+    }
+
+    /**
+     * Migrates the workspace and hotseat in case their sizes changed.
+     *
+     * @return false if the migration failed.
+     */
+    public static boolean migrateGridIfNeeded(Context context) {
+        // To be implemented.
+        return true;
+    }
+}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 95268d0..4961432 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static android.graphics.BitmapFactory.decodeByteArray;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
@@ -26,7 +28,6 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.CursorWrapper;
-import android.graphics.BitmapFactory;
 import android.os.UserHandle;
 import android.provider.BaseColumns;
 import android.text.TextUtils;
@@ -166,37 +167,30 @@
      */
     protected boolean loadIcon(WorkspaceItemInfo info) {
         try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
-            return loadIcon(info, li);
-        }
-    }
-
-    /**
-     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
-     */
-    protected boolean loadIcon(WorkspaceItemInfo info, LauncherIcons li) {
-        if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
-            String packageName = getString(iconPackageIndex);
-            String resourceName = getString(iconResourceIndex);
-            if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
-                info.iconResource = new ShortcutIconResource();
-                info.iconResource.packageName = packageName;
-                info.iconResource.resourceName = resourceName;
-                BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
-                if (iconInfo != null) {
-                    info.bitmap = iconInfo;
-                    return true;
+            if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
+                String packageName = getString(iconPackageIndex);
+                String resourceName = getString(iconResourceIndex);
+                if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) {
+                    info.iconResource = new ShortcutIconResource();
+                    info.iconResource.packageName = packageName;
+                    info.iconResource.resourceName = resourceName;
+                    BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+                    if (iconInfo != null) {
+                        info.bitmap = iconInfo;
+                        return true;
+                    }
                 }
             }
-        }
 
-        // Failed to load from resource, try loading from DB.
-        byte[] data = getBlob(iconIndex);
-        try {
-            info.bitmap = li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length));
-            return true;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to decode byte array for info " + info, e);
-            return false;
+            // Failed to load from resource, try loading from DB.
+            byte[] data = getBlob(iconIndex);
+            try {
+                info.bitmap = li.createIconBitmap(decodeByteArray(data, 0, data.length));
+                return true;
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to decode byte array for info " + info, e);
+                return false;
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 23ec459..6223a23 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
@@ -35,7 +36,6 @@
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.ShortcutInfo;
-import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -50,7 +50,6 @@
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherModel;
@@ -60,12 +59,12 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderGridOrganizer;
+import com.android.launcher3.folder.FolderNameInfo;
 import com.android.launcher3.folder.FolderNameProvider;
-import com.android.launcher3.icons.ComponentWithLabel;
-import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon.ComponentWithIconCachingLogic;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.icons.LauncherActivityCachingLogic;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.icons.ShortcutCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.logging.FileLog;
@@ -94,7 +93,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CancellationException;
-import java.util.function.Supplier;
 
 /**
  * Runnable for the thread that loads the contents of the launcher:
@@ -170,32 +168,15 @@
     }
 
     public void run() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "LoaderTask1 " + this);
-        }
         synchronized (this) {
             // Skip fast if we are already stopped.
             if (mStopped) {
                 return;
             }
         }
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "LoaderTask2 " + this);
-        }
 
         Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
-        TimingLogger logger = TestProtocol.sDebugTracing ?
-                new TimingLogger(TAG, "run") {
-                    @Override
-                    public void addSplit(String splitLabel) {
-                        super.addSplit(splitLabel);
-                        Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                                "LoaderTask.addSplit " + splitLabel);
-                    }
-                }
-                : new TimingLogger(TAG, "run");
+        TimingLogger logger = new TimingLogger(TAG, "run");
         try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
             List<ShortcutInfo> allShortcuts = new ArrayList<>();
             loadWorkspace(allShortcuts);
@@ -263,7 +244,8 @@
             verifyNotStopped();
 
             // fourth step
-            List<ComponentWithLabel> allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null);
+            List<ComponentWithLabelAndIcon> allWidgetsList =
+                    mBgDataModel.widgetsModel.update(mApp, null);
             logger.addSplit("load widgets");
 
             verifyNotStopped();
@@ -271,8 +253,9 @@
             logger.addSplit("bindWidgets");
             verifyNotStopped();
 
-            updateHandler.updateIcons(allWidgetsList, new ComponentCachingLogic(
-                    mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
+            updateHandler.updateIcons(allWidgetsList,
+                    new ComponentWithIconCachingLogic(mApp.getContext(), true),
+                    mApp.getModel()::onWidgetLabelsUpdated);
             logger.addSplit("save widgets in icon cache");
 
             // fifth step
@@ -284,10 +267,6 @@
             updateHandler.finish();
             logger.addSplit("finish icon update");
 
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                        "LoaderTask3 " + this);
-            }
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
@@ -320,7 +299,9 @@
             clearDb = true;
         }
 
-        if (!clearDb && !GridSizeMigrationTask.migrateGridIfNeeded(context)) {
+        if (!clearDb && (MULTI_DB_GRID_MIRATION_ALGO.get()
+                ? !GridSizeMigrationTaskV2.migrateGridIfNeeded(context)
+                : !GridSizeMigrationTask.migrateGridIfNeeded(context))) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
@@ -544,17 +525,10 @@
                                         continue;
                                     }
                                     info = new WorkspaceItemInfo(pinnedShortcut, context);
-                                    final WorkspaceItemInfo finalInfo = info;
-
-                                    LauncherIcons li = LauncherIcons.obtain(context);
                                     // If the pinned deep shortcut is no longer published,
                                     // use the last saved icon instead of the default.
-                                    Supplier<ItemInfoWithIcon> fallbackIconProvider = () ->
-                                            c.loadIcon(finalInfo, li) ? finalInfo : null;
-                                    info.bitmap = li.createShortcutIcon(
-                                            pinnedShortcut, true /* badged */,
-                                            fallbackIconProvider);
-                                    li.recycle();
+                                    mIconCache.getShortcutIcon(info, pinnedShortcut, c::loadIcon);
+
                                     if (pmHelper.isAppSuspended(
                                             pinnedShortcut.getPackage(), info.user)) {
                                         info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
@@ -854,23 +828,23 @@
 
     private void setIgnorePackages(IconCacheUpdateHandler updateHandler) {
         // Ignore packages which have a promise icon.
-        HashSet<String> packagesToIgnore = new HashSet<>();
         synchronized (mBgDataModel) {
             for (ItemInfo info : mBgDataModel.itemsIdMap) {
                 if (info instanceof WorkspaceItemInfo) {
                     WorkspaceItemInfo si = (WorkspaceItemInfo) info;
                     if (si.isPromise() && si.getTargetComponent() != null) {
-                        packagesToIgnore.add(si.getTargetComponent().getPackageName());
+                        updateHandler.addPackagesToIgnore(
+                                si.user, si.getTargetComponent().getPackageName());
                     }
                 } else if (info instanceof LauncherAppWidgetInfo) {
                     LauncherAppWidgetInfo lawi = (LauncherAppWidgetInfo) info;
                     if (lawi.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) {
-                        packagesToIgnore.add(lawi.providerName.getPackageName());
+                        updateHandler.addPackagesToIgnore(
+                                lawi.user, lawi.providerName.getPackageName());
                     }
                 }
             }
         }
-        updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore);
     }
 
     private List<LauncherActivityInfo> loadAllApps() {
@@ -927,15 +901,18 @@
     }
 
     private void loadFolderNames() {
-        FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext());
+        FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
+                mBgAllAppsList.data, mBgDataModel.folders);
 
         synchronized (mBgDataModel) {
             for (int i = 0; i < mBgDataModel.folders.size(); i++) {
-                String[] suggestedOut = new String[FolderNameProvider.SUGGEST_MAX];
+                FolderNameInfo[] suggestionInfos =
+                        new FolderNameInfo[FolderNameProvider.SUGGEST_MAX];
                 FolderInfo info = mBgDataModel.folders.valueAt(i);
                 if (info.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), info.contents, suggestedOut);
-                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestedOut);
+                    provider.getSuggestedFolderName(mApp.getContext(), info.contents,
+                            suggestionInfos);
+                    info.suggestedFolderNames = new Intent().putExtra("suggest", suggestionInfos);
                 }
             }
         }
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 628dd95..1473124 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -97,7 +97,7 @@
                         return Integer.compare(lhs.screenId, rhs.screenId);
                     }
                     default:
-                        if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                        if (FeatureFlags.IS_STUDIO_BUILD) {
                             throw new RuntimeException(
                                     "Unexpected container type when sorting workspace items.");
                         }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index ccd1554..27fa580 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -115,8 +115,9 @@
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
             // check all the data is consistent
-            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_DOGFOOD_BUILD &&
-                    modelItem instanceof WorkspaceItemInfo && item instanceof WorkspaceItemInfo) {
+            if (!Utilities.IS_DEBUG_DEVICE && !FeatureFlags.IS_STUDIO_BUILD
+                    && modelItem instanceof WorkspaceItemInfo
+                    && item instanceof WorkspaceItemInfo) {
                 if (modelItem.title.toString().equals(item.title.toString()) &&
                         modelItem.getIntent().filterEquals(item.getIntent()) &&
                         modelItem.id == item.id &&
@@ -320,7 +321,7 @@
      */
     public void prepareToUndoDelete() {
         if (!mPreparingToUndo) {
-            if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_DOGFOOD_BUILD) {
+            if (!mDeleteRunnables.isEmpty() && FeatureFlags.IS_STUDIO_BUILD) {
                 throw new IllegalStateException("There are still uncommitted delete operations!");
             }
             mDeleteRunnables.clear();
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index b0e7a69..3f79ad0 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.util.ItemInfoMatcher;
@@ -89,12 +88,7 @@
                 }
                 for (final WorkspaceItemInfo workspaceItemInfo : workspaceItemInfos) {
                     workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                    // If the shortcut is pinned but no longer has an icon in the system,
-                    // keep the current icon instead of reverting to the default icon.
-                    LauncherIcons li = LauncherIcons.obtain(context);
-                    workspaceItemInfo.bitmap = li.createShortcutIcon(
-                            fullDetails, true, () -> workspaceItemInfo);
-                    li.recycle();
+                    app.getIconCache().getShortcutIcon(workspaceItemInfo, fullDetails);
                     updatedWorkspaceItemInfos.add(workspaceItemInfo);
                 }
             }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index d527423..a3adc82 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -26,7 +26,6 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
@@ -89,11 +88,7 @@
                     }
                     si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER;
                     si.updateFromDeepShortcutInfo(shortcut, context);
-                    // If the shortcut is pinned but no longer has an icon in the system,
-                    // keep the current icon instead of reverting to the default icon.
-                    LauncherIcons li = LauncherIcons.obtain(context);
-                    si.bitmap = li.createShortcutIcon(shortcut, true, () -> si);
-                    li.recycle();
+                    app.getIconCache().getShortcutIcon(si, shortcut);
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
                 }
diff --git a/src/com/android/launcher3/pm/PinRequestHelper.java b/src/com/android/launcher3/pm/PinRequestHelper.java
index a15399d..74a5a31 100644
--- a/src/com/android/launcher3/pm/PinRequestHelper.java
+++ b/src/com/android/launcher3/pm/PinRequestHelper.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.ShortcutUtil.fetchAndUpdateShortcutIconAsync;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -32,8 +31,7 @@
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.ShortcutCachingLogic;
 
 public class PinRequestHelper {
 
@@ -82,16 +80,10 @@
 
             ShortcutInfo si = request.getShortcutInfo();
             WorkspaceItemInfo info = new WorkspaceItemInfo(si, context);
-            // Apply the unbadged icon and fetch the actual icon asynchronously.
-            if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
-                fetchAndUpdateShortcutIconAsync(context, info, si, false);
-            } else {
-                LauncherIcons li = LauncherIcons.obtain(context);
-                info.bitmap = li.createShortcutIcon(si, false /* badged */);
-                li.recycle();
-                LauncherAppState.getInstance(context).getModel()
-                        .updateAndBindWorkspaceItem(info, si);
-            }
+            // Apply the unbadged icon synchronously using the caching logic directly and
+            // fetch the actual icon asynchronously.
+            info.bitmap = new ShortcutCachingLogic().loadIcon(context, si);
+            LauncherAppState.getInstance(context).getModel().updateAndBindWorkspaceItem(info, si);
             return info;
         } else {
             return null;
diff --git a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
index b563171..ac0e065 100644
--- a/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
+++ b/src/com/android/launcher3/pm/ShortcutConfigActivityInfo.java
@@ -41,7 +41,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -51,7 +51,7 @@
 /**
  * Wrapper class for representing a shortcut configure activity.
  */
-public abstract class ShortcutConfigActivityInfo implements ComponentWithLabel {
+public abstract class ShortcutConfigActivityInfo implements ComponentWithLabelAndIcon {
 
     private static final String TAG = "SCActivityInfo";
 
@@ -77,6 +77,7 @@
         return LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
     }
 
+    @Override
     public abstract Drawable getFullResIcon(IconCache cache);
 
     /**
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 9faeb40..fdcf04f 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -26,8 +26,9 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.notification.NotificationInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
@@ -153,13 +154,11 @@
             String shortcutIdToDeDupe = notificationKeys.isEmpty() ? null
                     : notificationKeys.get(0).shortcutId;
             shortcuts = PopupPopulator.sortAndFilterShortcuts(shortcuts, shortcutIdToDeDupe);
+            IconCache cache = LauncherAppState.getInstance(launcher).getIconCache();
             for (int i = 0; i < shortcuts.size() && i < shortcutViews.size(); i++) {
                 final ShortcutInfo shortcut = shortcuts.get(i);
                 final WorkspaceItemInfo si = new WorkspaceItemInfo(shortcut, launcher);
-                // Use unbadged icon for the menu.
-                LauncherIcons li = LauncherIcons.obtain(launcher);
-                si.bitmap = li.createShortcutIcon(shortcut, false /* badged */);
-                li.recycle();
+                cache.getUnbadgedShortcutIcon(si, shortcut);
                 si.rank = i;
 
                 final DeepShortcutView view = shortcutViews.get(i);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 407ff31..0fe3673 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -107,6 +107,7 @@
      */
     private void backupWorkspace(Context context, SQLiteDatabase db) throws Exception {
         InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
+        // TODO(pinyaoting): Support backing up workspace with multiple grid options.
         new GridBackupTable(context, db, idp.numHotseatIcons, idp.numColumns, idp.numRows)
                 .doBackup(getDefaultProfileId(db), GridBackupTable.OPTION_REQUIRES_SANITIZATION);
     }
@@ -114,9 +115,10 @@
     private void restoreWorkspace(@NonNull Context context, @NonNull SQLiteDatabase db,
             @NonNull DatabaseHelper helper, @NonNull BackupManager backupManager)
             throws Exception {
+        // TODO(pinyaoting): Support restoring workspace with multiple grid options.
         final InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
-        GridBackupTable backupTable = new GridBackupTable(context, db,
-                idp.numHotseatIcons, idp.numColumns, idp.numRows);
+        GridBackupTable backupTable = new GridBackupTable(context, db, idp.numHotseatIcons,
+                idp.numColumns, idp.numRows);
         if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) {
             sanitizeDB(helper, db, backupManager);
             LauncherAppState.getInstance(context).getModel().forceReload();
diff --git a/src/com/android/launcher3/settings/NotificationDotsPreference.java b/src/com/android/launcher3/settings/NotificationDotsPreference.java
index f30470a..a91303a 100644
--- a/src/com/android/launcher3/settings/NotificationDotsPreference.java
+++ b/src/com/android/launcher3/settings/NotificationDotsPreference.java
@@ -20,7 +20,6 @@
 
 import android.app.AlertDialog;
 import android.app.Dialog;
-import android.app.DialogFragment;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -30,13 +29,14 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
 import com.android.launcher3.R;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.SecureSettingsObserver;
 
-import androidx.preference.Preference;
-import androidx.preference.PreferenceViewHolder;
-
 /**
  * A {@link Preference} for indicating notification dots status.
  * Also has utility methods for updating UI based on dots status changes.
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 4af5e0a..4e49c6e 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -17,16 +17,22 @@
 
 import static android.graphics.Bitmap.Config.ARGB_8888;
 
+import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
+import android.annotation.TargetApi;
+import android.app.Activity;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.graphics.Insets;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
 import android.system.Os;
 import android.util.Log;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.annotation.Keep;
 
@@ -36,14 +42,19 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.LinkedList;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.function.Supplier;
 
+/**
+ * Class to handle requests from tests
+ */
+@TargetApi(Build.VERSION_CODES.Q)
 public class TestInformationHandler implements ResourceBasedOverride {
 
     public static TestInformationHandler newInstance(Context context) {
@@ -54,7 +65,6 @@
     protected Context mContext;
     protected DeviceProfile mDeviceProfile;
     protected LauncherAppState mLauncherAppState;
-    protected Launcher mLauncher;
     private static LinkedList mLeaks;
 
     public void init(Context context) {
@@ -62,35 +72,31 @@
         mDeviceProfile = InvariantDeviceProfile.INSTANCE.
                 get(context).getDeviceProfile(context);
         mLauncherAppState = LauncherAppState.getInstanceNoCreate();
-        mLauncher = Launcher.ACTIVITY_TRACKER.getCreatedActivity();
     }
 
     public Bundle call(String method) {
         final Bundle response = new Bundle();
         switch (method) {
             case TestProtocol.REQUEST_ALL_APPS_TO_OVERVIEW_SWIPE_HEIGHT: {
-                if (mLauncher == null) return null;
-
-                final float progress = LauncherState.OVERVIEW.getVerticalProgress(mLauncher)
-                        - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
-                final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                break;
+                return getLauncherUIProperty(Bundle::putInt, l -> {
+                    final float progress = LauncherState.OVERVIEW.getVerticalProgress(l)
+                            - LauncherState.ALL_APPS.getVerticalProgress(l);
+                    final float distance = l.getAllAppsController().getShiftRange() * progress;
+                    return (int) distance;
+                });
             }
 
             case TestProtocol.REQUEST_HOME_TO_ALL_APPS_SWIPE_HEIGHT: {
-                if (mLauncher == null) return null;
-
-                final float progress = LauncherState.NORMAL.getVerticalProgress(mLauncher)
-                        - LauncherState.ALL_APPS.getVerticalProgress(mLauncher);
-                final float distance = mLauncher.getAllAppsController().getShiftRange() * progress;
-                response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD, (int) distance);
-                break;
+                return getLauncherUIProperty(Bundle::putInt, l -> {
+                    final float progress = LauncherState.NORMAL.getVerticalProgress(l)
+                            - LauncherState.ALL_APPS.getVerticalProgress(l);
+                    final float distance = l.getAllAppsController().getShiftRange() * progress;
+                    return (int) distance;
+                });
             }
 
             case TestProtocol.REQUEST_IS_LAUNCHER_INITIALIZED: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, isLauncherInitialized());
-                break;
+                return getUIProperty(Bundle::putBoolean, t -> isLauncherInitialized(), () -> true);
             }
 
             case TestProtocol.REQUEST_ENABLE_DEBUG_TRACING:
@@ -102,40 +108,33 @@
                 break;
 
             case TestProtocol.REQUEST_FREEZE_APP_LIST:
-                MAIN_EXECUTOR.execute(() ->
-                        mLauncher.getAppsView().getAppsStore().enableDeferUpdates(
-                                AllAppsStore.DEFER_UPDATES_TEST));
-                break;
-
+                return getLauncherUIProperty(Bundle::putBoolean, l -> {
+                    l.getAppsView().getAppsStore().enableDeferUpdates(DEFER_UPDATES_TEST);
+                    return true;
+                });
             case TestProtocol.REQUEST_UNFREEZE_APP_LIST:
-                MAIN_EXECUTOR.execute(() ->
-                        mLauncher.getAppsView().getAppsStore().disableDeferUpdates(
-                                AllAppsStore.DEFER_UPDATES_TEST));
-                break;
+                return getLauncherUIProperty(Bundle::putBoolean, l -> {
+                    l.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
+                    return true;
+                });
 
             case TestProtocol.REQUEST_APP_LIST_FREEZE_FLAGS: {
-                try {
-                    final int deferUpdatesFlags = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.getAppsView().getAppsStore().getDeferUpdatesFlags()).get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                            deferUpdatesFlags);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                break;
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> l.getAppsView().getAppsStore().getDeferUpdatesFlags());
             }
 
             case TestProtocol.REQUEST_APPS_LIST_SCROLL_Y: {
-                try {
-                    final int scroll = MAIN_EXECUTOR.submit(() ->
-                            mLauncher.getAppsView().getActiveRecyclerView().getCurrentScrollY())
-                            .get();
-                    response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
-                            scroll);
-                } catch (ExecutionException | InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                break;
+                return getLauncherUIProperty(Bundle::putInt,
+                        l -> l.getAppsView().getActiveRecyclerView().getCurrentScrollY());
+            }
+
+            case TestProtocol.REQUEST_WINDOW_INSETS: {
+                return getUIProperty(Bundle::putParcelable, a -> {
+                    WindowInsets insets = a.getWindow()
+                            .getDecorView().getRootWindowInsets();
+                    return Insets.max(
+                            insets.getSystemGestureInsets(), insets.getSystemWindowInsets());
+                }, this::getCurrentActivity);
             }
 
             case TestProtocol.REQUEST_PID: {
@@ -176,7 +175,6 @@
 
             case TestProtocol.REQUEST_VIEW_LEAK: {
                 if (mLeaks == null) mLeaks = new LinkedList();
-
                 mLeaks.add(new View(mContext));
                 break;
             }
@@ -191,15 +189,14 @@
     }
 
     protected boolean isLauncherInitialized() {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "isLauncherInitialized " + Launcher.ACTIVITY_TRACKER.getCreatedActivity() + ", "
-                            + LauncherAppState.getInstance(mContext).getModel().isModelLoaded());
-        }
         return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
                 || LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
     }
 
+    protected Activity getCurrentActivity() {
+        return Launcher.ACTIVITY_TRACKER.getCreatedActivity();
+    }
+
     private static void runGcAndFinalizersSync() {
         Runtime.getRuntime().gc();
         Runtime.getRuntime().runFinalization();
@@ -216,6 +213,47 @@
         }
     }
 
+    /**
+     * Returns the result by getting a Launcher property on UI thread
+     */
+    public static <T> Bundle getLauncherUIProperty(
+            BundleSetter<T> bundleSetter, Function<Launcher, T> provider) {
+        return getUIProperty(bundleSetter, provider, Launcher.ACTIVITY_TRACKER::getCreatedActivity);
+    }
+
+    /**
+     * Returns the result by getting a generic property on UI thread
+     */
+    private static <S, T> Bundle getUIProperty(
+            BundleSetter<T> bundleSetter, Function<S, T> provider, Supplier<S> targetSupplier) {
+        try {
+            return MAIN_EXECUTOR.submit(() -> {
+                S target = targetSupplier.get();
+                if (target == null) {
+                    return null;
+                }
+                T value = provider.apply(target);
+                Bundle response = new Bundle();
+                bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
+                return response;
+            }).get();
+        } catch (ExecutionException | InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Generic interface for setting a fiend in bundle
+     * @param <T> the type of value being set
+     */
+    public interface BundleSetter<T> {
+
+        /**
+         * Sets any generic property to the bundle
+         */
+        void set(Bundle b, String key, T value);
+    }
+
     // Create the observer in the scope of a method to minimize the chance that
     // it remains live in a DEX/machine register at the point of the fence guard.
     // This must be kept to avoid R8 inlining it.
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index fd066c1..d522d81 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -17,13 +17,30 @@
 package com.android.launcher3.testing;
 
 import android.util.Log;
+import android.view.MotionEvent;
 
 import com.android.launcher3.Utilities;
 
 public final class TestLogging {
-    public static void recordEvent(String event) {
+    private static void recordEventSlow(String sequence, String event) {
+        Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+    }
+
+    public static void recordEvent(String sequence, String event) {
         if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            Log.d(TestProtocol.TAPL_EVENTS_TAG, event);
+            recordEventSlow(sequence, event);
+        }
+    }
+
+    public static void recordEvent(String sequence, String message, Object parameter) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            recordEventSlow(sequence, message + ": " + parameter);
+        }
+    }
+
+    public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS && event.getAction() != MotionEvent.ACTION_MOVE) {
+            recordEventSlow(sequence, message + ": " + event);
         }
     }
 }
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 2f053c9..f995c61 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -33,6 +33,8 @@
     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";
 
     public static String stateOrdinalToString(int ordinal) {
         switch (ordinal) {
@@ -75,8 +77,7 @@
     public static final String REQUEST_UNFREEZE_APP_LIST = "unfreeze-app-list";
     public static final String REQUEST_APP_LIST_FREEZE_FLAGS = "app-list-freeze-flags";
     public static final String REQUEST_APPS_LIST_SCROLL_Y = "apps-list-scroll-y";
-    public static final String REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN = "overview-left-margin";
-    public static final String REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN = "overview-right-margin";
+    public static final String REQUEST_WINDOW_INSETS = "window-insets";
     public static final String REQUEST_PID = "pid";
     public static final String REQUEST_TOTAL_PSS_KB = "total_pss";
     public static final String REQUEST_JAVA_LEAK = "java-leak";
@@ -92,5 +93,4 @@
 
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String APP_NOT_DISABLED = "b/139891609";
-    public static final String LAUNCHER_DIDNT_INITIALIZE = "b/148313079";
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 7ae0526..9df6241 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -23,7 +23,7 @@
 import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
 import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
@@ -435,7 +435,7 @@
         updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
                 targetState, velocity, fling);
         mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity);
-        if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
+        if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
             mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
         }
         anim.start();
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index b83c8fc..499f655 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -45,13 +45,7 @@
     }
 
     public void onActivityDestroyed(T activity) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed");
-        }
         if (mCurrentActivity.get() == activity) {
-            if (TestProtocol.sDebugTracing) {
-                Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed: clear");
-            }
             mCurrentActivity.clear();
         }
     }
@@ -116,10 +110,6 @@
     }
 
     public boolean handleCreate(T activity) {
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
-                    "ActivityTracker.handleCreate " + mCurrentActivity.get() + " => " + activity);
-        }
         mCurrentActivity = new WeakReference<>(activity);
         return handleIntent(activity, activity.getIntent(), false, false);
     }
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index 4a4a5ca..fcb96d7 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
 import java.io.ByteArrayOutputStream;
@@ -67,7 +66,7 @@
      * Utility method to debug binary data
      */
     public static String createTempFile(Context context, byte[] data) {
-        if (!FeatureFlags.IS_DOGFOOD_BUILD) {
+        if (!FeatureFlags.IS_STUDIO_BUILD) {
             throw new IllegalStateException("Method only allowed in development mode");
         }
 
@@ -87,7 +86,7 @@
             try {
                 c.close();
             } catch (IOException e) {
-                if (FeatureFlags.IS_DOGFOOD_BUILD) {
+                if (FeatureFlags.IS_STUDIO_BUILD) {
                     Log.d(TAG, "Error closing", e);
                 }
             }
diff --git a/src/com/android/launcher3/util/Preconditions.java b/src/com/android/launcher3/util/Preconditions.java
index ed66422..63d56e4 100644
--- a/src/com/android/launcher3/util/Preconditions.java
+++ b/src/com/android/launcher3/util/Preconditions.java
@@ -28,25 +28,25 @@
 public class Preconditions {
 
     public static void assertNotNull(Object o) {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && o == null) {
+        if (FeatureFlags.IS_STUDIO_BUILD && o == null) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertWorkerThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(MODEL_EXECUTOR.getLooper())) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertUIThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && !isSameLooper(Looper.getMainLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && !isSameLooper(Looper.getMainLooper())) {
             throw new IllegalStateException();
         }
     }
 
     public static void assertNonUiThread() {
-        if (FeatureFlags.IS_DOGFOOD_BUILD && isSameLooper(Looper.getMainLooper())) {
+        if (FeatureFlags.IS_STUDIO_BUILD && isSameLooper(Looper.getMainLooper())) {
             throw new IllegalStateException();
         }
     }
diff --git a/src/com/android/launcher3/util/ShortcutUtil.java b/src/com/android/launcher3/util/ShortcutUtil.java
index a03b743..49c97da 100644
--- a/src/com/android/launcher3/util/ShortcutUtil.java
+++ b/src/com/android/launcher3/util/ShortcutUtil.java
@@ -15,19 +15,10 @@
  */
 package com.android.launcher3.util;
 
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-
-import androidx.annotation.NonNull;
-
 import com.android.launcher3.ItemInfo;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.shortcuts.ShortcutKey;
 
@@ -70,21 +61,6 @@
                 && info instanceof WorkspaceItemInfo;
     }
 
-    /**
-     * Fetch the shortcut icon in background, then update the UI.
-     */
-    public static void fetchAndUpdateShortcutIconAsync(
-            @NonNull Context context, @NonNull WorkspaceItemInfo info, @NonNull ShortcutInfo si,
-            boolean badged) {
-        MODEL_EXECUTOR.execute(() -> {
-            try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                info.bitmap = li.createShortcutIcon(si, badged, null);
-                LauncherAppState.getInstance(context).getModel()
-                        .updateAndBindWorkspaceItem(info, si);
-            }
-        });
-    }
-
     private static boolean isActive(ItemInfo info) {
         boolean isLoading = info instanceof WorkspaceItemInfo
                 && ((WorkspaceItemInfo) info).hasPromiseIconUi();
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index f8add9a..9ac8230 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -119,6 +119,13 @@
         mWorkModeSwitch.refresh();
     }
 
+    /**
+     * Returns work mode switch
+     */
+    public WorkModeSwitch getWorkModeSwitch() {
+        return mWorkModeSwitch;
+    }
+
     private boolean shouldShowWorkFooter() {
         Launcher launcher = Launcher.getLauncher(getContext());
         return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
index 22c0874..282867a 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/WidgetsModel.java
@@ -21,7 +21,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.compat.AlphabeticIndexCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.icons.ComponentWithLabel;
+import com.android.launcher3.icons.ComponentWithLabelAndIcon;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.MultiHashMap;
@@ -85,12 +85,13 @@
      * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise
      *                    only widgets and shortcuts associated with the package/user are.
      */
-    public List<ComponentWithLabel> update(LauncherAppState app, @Nullable PackageUserKey packageUser) {
+    public List<ComponentWithLabelAndIcon> update(
+            LauncherAppState app, @Nullable PackageUserKey packageUser) {
         Preconditions.assertWorkerThread();
 
         Context context = app.getContext();
         final ArrayList<WidgetItem> widgetsAndShortcuts = new ArrayList<>();
-        List<ComponentWithLabel> updatedItems = new ArrayList<>();
+        List<ComponentWithLabelAndIcon> updatedItems = new ArrayList<>();
         try {
             InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
             PackageManager pm = app.getContext().getPackageManager();
@@ -114,7 +115,7 @@
             }
             setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser);
         } catch (Exception e) {
-            if (!FeatureFlags.IS_DOGFOOD_BUILD && Utilities.isBinderSizeError(e)) {
+            if (!FeatureFlags.IS_STUDIO_BUILD && Utilities.isBinderSizeError(e)) {
                 // the returned value may be incomplete and will not be refreshed until the next
                 // time Launcher starts.
                 // TODO: after figuring out a repro step, introduce a dirty bit to check when
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java b/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
deleted file mode 100644
index b1a67e9..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/DisplayRotationListener.java
+++ /dev/null
@@ -1,37 +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.launcher3.uioverrides;
-
-import android.content.Context;
-import android.view.OrientationEventListener;
-
-/**
- * Utility class for listening for rotation changes
- */
-public class DisplayRotationListener extends OrientationEventListener {
-
-    private final Runnable mCallback;
-
-    public DisplayRotationListener(Context context, Runnable callback) {
-        super(context);
-        mCallback = callback;
-    }
-
-    @Override
-    public void onOrientationChanged(int i) {
-        mCallback.run();
-    }
-}
diff --git a/tests/Android.mk b/tests/Android.mk
index d1a6c06..a9fff8e 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -51,7 +51,7 @@
     androidx.test.rules \
     androidx.test.uiautomator_uiautomator \
     mockito-target-minus-junit4 \
-    launcher-log-protos-lite
+    launcher_log_protos_lite
 
 ifneq (,$(wildcard frameworks/base))
     LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 4b72882..54caf1e 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -18,9 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -39,7 +36,6 @@
 import com.android.launcher3.tapl.AppIconMenuItem;
 import com.android.launcher3.tapl.Widgets;
 import com.android.launcher3.tapl.Workspace;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.widget.WidgetsFullSheet;
 import com.android.launcher3.widget.WidgetsRecyclerView;
@@ -117,9 +113,7 @@
         mLauncher.pressHome();
     }
 
-    // b/146432215: remove @Stability after 2/1/2020 if this test doesn't flake
     @Test
-    @Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void testOpenHomeSettingsFromWorkspace() {
         mDevice.pressMenu();
         mDevice.waitForIdle();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index d9edc35..5aa0090 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,17 +16,32 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.views.WorkFooterContainer;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+import java.util.Objects;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class WorkTabTest extends AbstractLauncherUiTest {
@@ -52,16 +67,50 @@
     }
 
     @Test
+    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void workTabExists() {
         mDevice.pressHome();
-        waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
         executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
 
-        /*
-        assertTrue("Personal tab is missing", waitForLauncherCondition(
-                launcher -> launcher.getAppsView().isPersonalTabVisible()));
-        assertTrue("Work tab is missing", waitForLauncherCondition(
-                launcher -> launcher.getAppsView().isWorkTabVisible()));
-        */
+        waitForLauncherCondition("Personal tab is missing",
+                launcher -> launcher.getAppsView().isPersonalTabVisible());
+        waitForLauncherCondition("Work tab is missing",
+                launcher -> launcher.getAppsView().isWorkTabVisible());
     }
+
+    @Test
+    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
+    public void toggleWorks() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
+        getOnceNotNull("Apps view did not bind",
+                launcher -> launcher.getAppsView().getWorkFooterContainer());
+
+        UserManager userManager = getFromLauncher(l -> l.getSystemService(UserManager.class));
+        assertEquals(2, userManager.getUserProfiles().size());
+        UserHandle workProfile = getFromLauncher(l -> {
+            UserHandle myHandle = Process.myUserHandle();
+            List<UserHandle> userProfiles = userManager.getUserProfiles();
+            return userProfiles.get(0) == myHandle ? userProfiles.get(1) : userProfiles.get(0);
+        });
+
+        waitForLauncherCondition("work profile can't be turned off",
+                l -> userManager.requestQuietModeEnabled(true, workProfile));
+
+        assertTrue(userManager.isQuietModeEnabled(workProfile));
+        executeOnLauncher(launcher -> {
+            WorkFooterContainer wf = launcher.getAppsView().getWorkFooterContainer();
+            ((AllAppsPagedView) launcher.getAppsView().getContentView()).snapToPageImmediately(
+                    AllAppsContainerView.AdapterHolder.WORK);
+            wf.getWorkModeSwitch().toggle();
+        });
+        waitForLauncherCondition("Work toggle did not work",
+                l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
+    }
+
 }
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index d6dfdd9..5880eb6 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -55,7 +55,7 @@
     private static final ExceptionMatch[] EXCEPTION_MATCHES = {
             new ExceptionMatch(
                     "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
-                            + "non-Launcher system error: Phone is locked",
+                            + "non-Launcher system error: (Phone is locked|Screen is empty)",
                     new LogcatMatch[]{
                             new LogcatMatch(
                                     "BroadcastQueue: Can't deliver broadcast to com.android"
@@ -87,6 +87,11 @@
             return 145935261;
         }
 
+        if (matches("java\\.lang\\.AssertionError\\: http\\:\\/\\/go\\/tapl \\: want to get "
+                + "workspace object; Presence of recents button doesn't match the interaction "
+                + "mode, mode\\=ZERO_BUTTON, hasRecents\\=true", exception)) {
+            return 148422894;
+        }
 
         final String logSinceBoot;
         try {
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index 02d07bb..cdda0f0 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -8,7 +8,6 @@
 
 import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -16,7 +15,6 @@
 
 public class FailureWatcher extends TestWatcher {
     private static final String TAG = "FailureWatcher";
-    private static boolean sHadFailedTestDeinitialization;
     final private UiDevice mDevice;
 
     public FailureWatcher(UiDevice device) {
@@ -62,35 +60,4 @@
 
         device.takeScreenshot(new File(pathname));
     }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (sHadFailedTestDeinitialization) {
-                    Log.d(TAG, "Skipping due to a recent test deinitialization failure: " +
-                            description.getDisplayName());
-                    return;
-                }
-
-                try {
-                    base.evaluate();
-                } catch (Throwable e) {
-                    final String stackTrace = Log.getStackTraceString(e);
-                    if (!stackTrace.contains(
-                            "androidx.test.internal.runner.junit4.statement.RunBefores.evaluate")) {
-                        // Test failed to deinitialize. Since the global state is probably
-                        // corrupted, won't execute other tests.
-                        Log.d(TAG,
-                                "Detected an exception from test finalizer, will skip further "
-                                        + "tests: " + stackTrace);
-                        sHadFailedTestDeinitialization = true;
-                    }
-                    throw e;
-                }
-            }
-        };
-    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
index afb50e0..468f54c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
+++ b/tests/tapl/com/android/launcher3/tapl/AddToHomeScreenPrompt.java
@@ -21,6 +21,8 @@
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 public class AddToHomeScreenPrompt {
@@ -38,6 +40,13 @@
 
     public void addAutomatically() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            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);
+            }
             mLauncher.waitForObjectInContainer(
                     mWidgetCell.getParent().getParent().getParent().getParent(),
                     By.text(ADD_AUTOMATICALLY)).click();
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 3f814fd..8932291 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -22,6 +22,8 @@
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -56,6 +58,6 @@
 
     @Override
     protected void expectActivityStartEvents() {
-        mLauncher.expectEvent(START_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_EVENT);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index fadfd9f..f8dd89c 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -18,6 +18,8 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -45,6 +47,6 @@
 
     @Override
     protected void expectActivityStartEvents() {
-        mLauncher.expectEvent(START_SHORTCUT_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, START_SHORTCUT_EVENT);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 9f29a1a..2acab97 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -131,7 +131,7 @@
             }
 
             case THREE_BUTTON:
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(
                         () -> mLauncher.waitForSystemUiObject("recent_apps").click(),
                         OVERVIEW_STATE_ORDINAL);
@@ -195,14 +195,14 @@
             case THREE_BUTTON:
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
                 mLauncher.getOverview();
-                mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 recentsButton.click();
                 break;
         }
-        mLauncher.expectEvent(TASK_START_EVENT);
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
     }
 
     protected String getSwipeHeightRequestName() {
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index e5c83e2..a769acf 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -23,8 +23,6 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.TestProtocol;
-
 import java.util.Collections;
 import java.util.List;
 
@@ -58,9 +56,7 @@
                      mLauncher.addContextLayer("want to fling forward in overview")) {
             LauncherInstrumentation.log("Overview.flingForward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            final int leftMargin = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_OVERVIEW_LEFT_GESTURE_MARGIN).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            final int leftMargin = mLauncher.getTargetInsets().left;
             mLauncher.scroll(
                     overview, Direction.LEFT, new Rect(leftMargin + 1, 0, 0, 0), 20, false);
             verifyActiveContainer();
@@ -96,9 +92,7 @@
                      mLauncher.addContextLayer("want to fling backward in overview")) {
             LauncherInstrumentation.log("Overview.flingBackward before fling");
             final UiObject2 overview = verifyActiveContainer();
-            final int rightMargin = mLauncher.getTestInfo(
-                    TestProtocol.REQUEST_OVERVIEW_RIGHT_GESTURE_MARGIN).
-                    getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+            final int rightMargin = mLauncher.getTargetInsets().right;
             mLauncher.scroll(
                     overview, Direction.RIGHT, new Rect(0, 0, rightMargin + 1, 0), 20, false);
             verifyActiveContainer();
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 7c49f50..60ec3a4 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -33,6 +33,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
@@ -75,8 +76,10 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.Deque;
+import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -101,13 +104,17 @@
     static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
             "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
                     + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
-                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
+                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<sequence>[a-zA-Z]+) / "
+                    + "(?<event>.*)");
 
     private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
     private static final Pattern EVENT_TOUCH_UP = getTouchEventPattern("ACTION_UP");
     private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPattern("ACTION_CANCEL");
     private static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
 
+    static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
+    static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
+
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
     public enum ContainerType {
@@ -117,7 +124,7 @@
     public enum NavigationModel {ZERO_BUTTON, TWO_BUTTON, THREE_BUTTON}
 
     // Where the gesture happens: outside of Launcher, inside or from inside to outside.
-    enum GestureScope {
+    public enum GestureScope {
         OUTSIDE, INSIDE, INSIDE_TO_OUTSIDE
     }
 
@@ -146,7 +153,7 @@
         }
     }
 
-    interface Closable extends AutoCloseable {
+    public interface Closable extends AutoCloseable {
         void close();
     }
 
@@ -169,19 +176,27 @@
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
+    // Map from an event sequence name to an ordered list of expected events in that sequence.
     // Not null when we are collecting expected events to compare with actual ones.
-    private List<Pattern> mExpectedEvents;
+    private Map<String, List<Pattern>> mExpectedEvents;
 
     private Date mStartRecordingTime;
     private boolean mCheckEventsForSuccessfulGestures = false;
 
-    private static Pattern getTouchEventPattern(String action) {
+    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.
         return Pattern.compile(
-                "Touch event: MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
-                        +
-                        ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+                prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
+                        + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?pointerCount=1");
+    }
+
+    private static Pattern getTouchEventPattern(String action) {
+        return getTouchEventPattern("Touch event", action);
+    }
+
+    private static Pattern getTouchEventPatternTIS(String action) {
+        return getTouchEventPattern("TouchInteractionService.onInputEvent", action);
     }
 
     /**
@@ -257,6 +272,11 @@
         return getContext().getContentResolver().call(mTestProviderUri, request, null, null);
     }
 
+    Insets getTargetInsets() {
+        return getTestInfo(TestProtocol.REQUEST_WINDOW_INSETS)
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     void setActiveContainer(VisibleContainer container) {
         sActiveContainer = new WeakReference<>(container);
     }
@@ -341,7 +361,8 @@
     public void checkForAnomaly() {
         final String anomalyMessage = getAnomalyMessage();
         if (anomalyMessage != null) {
-            String message = "Tests are broken by a non-Launcher system error: " + anomalyMessage;
+            String message = "http://go/tapl : Tests are broken by a non-Launcher system error: "
+                    + anomalyMessage;
             log("Hierarchy dump for: " + message);
             dumpViewHierarchy();
 
@@ -489,6 +510,12 @@
         assertEquals("Unexpected display rotation",
                 mExpectedRotation, mDevice.getDisplayRotation());
 
+        // b/148422894
+        for (int i = 0; i != 600; ++i) {
+            if (getNavigationModeMismatchError() == null) break;
+            sleep(100);
+        }
+
         final String error = getNavigationModeMismatchError();
         assertTrue(error, error == null);
         log("verifyContainerType: " + containerType);
@@ -639,10 +666,14 @@
                         if (hasLauncherObject(CONTEXT_MENU_RES_ID) ||
                                 hasLauncherObject(WIDGETS_RES_ID)
                                         && !mDevice.isNaturalOrientation()) {
-                            expectEvent(EVENT_PILFER_POINTERS);
+                            expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
                         }
                     }
 
+                    if (getNavigationModel() == NavigationModel.TWO_BUTTON) {
+                        expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
+                        expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+                    }
                     runToState(
                             waitForSystemUiObject("home")::click,
                             NORMAL_STATE_ORDINAL,
@@ -922,8 +953,12 @@
     }
 
     void clickLauncherObject(UiObject2 object) {
-        expectEvent(LauncherInstrumentation.EVENT_TOUCH_DOWN);
-        expectEvent(LauncherInstrumentation.EVENT_TOUCH_UP);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_DOWN);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, LauncherInstrumentation.EVENT_TOUCH_UP);
+        if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+            expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
+            expectEvent(TestProtocol.SEQUENCE_TIS, LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
+        }
         object.click();
     }
 
@@ -1007,7 +1042,8 @@
 
     // Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
     // fixed interval each time.
-    void linearGesture(int startX, int startY, int endX, int endY, int steps, boolean slowDown,
+    public void linearGesture(int startX, int startY, int endX, int endY, int steps,
+            boolean slowDown,
             GestureScope gestureScope) {
         log("linearGesture: " + startX + ", " + startY + " -> " + endX + ", " + endY);
         final long downTime = SystemClock.uptimeMillis();
@@ -1059,22 +1095,28 @@
                 0, 0, 1.0f, 1.0f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
     }
 
-    void sendPointer(long downTime, long currentTime, int action, Point point,
+    public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         switch (action) {
             case MotionEvent.ACTION_DOWN:
                 if (gestureScope != GestureScope.OUTSIDE) {
-                    expectEvent(EVENT_TOUCH_DOWN);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
+                }
+                if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
                 }
                 break;
             case MotionEvent.ACTION_UP:
                 if (gestureScope != GestureScope.INSIDE) {
-                    expectEvent(EVENT_PILFER_POINTERS);
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_PILFER_POINTERS);
                 }
                 if (gestureScope != GestureScope.OUTSIDE) {
-                    expectEvent(gestureScope == GestureScope.INSIDE
+                    expectEvent(TestProtocol.SEQUENCE_MAIN, gestureScope == GestureScope.INSIDE
                             ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
                 }
+                if (getNavigationModel() != NavigationModel.THREE_BUTTON) {
+                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
+                }
                 break;
         }
 
@@ -1083,7 +1125,7 @@
         event.recycle();
     }
 
-    long movePointer(long downTime, long startTime, long duration, Point from, Point to,
+    public long movePointer(long downTime, long startTime, long duration, Point from, Point to,
             GestureScope gestureScope) {
         log("movePointer: " + from + " to " + to);
         final Point point = new Point();
@@ -1203,13 +1245,20 @@
         return tasks;
     }
 
-    private List<String> getEvents() {
-        final ArrayList<String> events = new ArrayList<>();
+    // Returns actual events retrieved from logcat. The return value's key set is the set of all
+    // sequence names that actually had at least one event, and the values are lists of events in
+    // the given sequence, in the order they were recorded.
+    private Map<String, List<String>> getEvents() {
+        final Map<String, List<String>> events = new HashMap<>();
         try {
-            final String logcatEvents = mDevice.executeShellCommand(
-                    "logcat -d -v year --pid=" + getPid() + " -t "
-                            + DATE_TIME_FORMAT.format(mStartRecordingTime).replaceAll(" ", "")
-                            + " -s " + TestProtocol.TAPL_EVENTS_TAG);
+            // Logcat may skip events after the specified time. Querying for events starting 1 min
+            // earlier.
+            final Date startTime = new Date(mStartRecordingTime.getTime() - 60000);
+            final String logcatCommand = "logcat -d -v year --pid=" + getPid() + " -t "
+                    + DATE_TIME_FORMAT.format(startTime).replaceAll(" ", "")
+                    + " -s " + TestProtocol.TAPL_EVENTS_TAG;
+            log("Events query command: " + logcatCommand);
+            final String logcatEvents = mDevice.executeShellCommand(logcatCommand);
             final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
             while (matcher.find()) {
                 // Skip events before recording start time.
@@ -1218,7 +1267,8 @@
                     continue;
                 }
 
-                events.add(matcher.group("event"));
+                eventsListForSequence(matcher.group("sequence"), events).add(
+                        matcher.group("event"));
             }
             return events;
         } catch (IOException e) {
@@ -1228,9 +1278,20 @@
         }
     }
 
+    // Returns an event list for a given sequence, adding it to the map as needed.
+    private static <T> List<T> eventsListForSequence(
+            String sequenceName, Map<String, List<T>> events) {
+        List<T> eventSequence = events.get(sequenceName);
+        if (eventSequence == null) {
+            eventSequence = new ArrayList<>();
+            events.put(sequenceName, eventSequence);
+        }
+        return eventSequence;
+    }
+
     private void startRecordingEvents() {
         Assert.assertTrue("Already recording events", mExpectedEvents == null);
-        mExpectedEvents = new ArrayList<>();
+        mExpectedEvents = new HashMap<>();
         mStartRecordingTime = new Date();
         log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
     }
@@ -1240,7 +1301,7 @@
         mStartRecordingTime = null;
     }
 
-    Closable eventsCheck() {
+    public Closable eventsCheck() {
         if ("com.android.launcher3".equals(getLauncherPackageName())) {
             // Not checking specific Launcher3 event sequences.
             return () -> {
@@ -1264,65 +1325,124 @@
             final String message = getEventMismatchMessage(true);
             if (message != null) {
                 Assert.fail(formatSystemHealthMessage(
-                        "http://go/tapl : unexpected event sequence: " + message));
+                        "http://go/tapl : successful gesture produced " + message));
             }
         };
     }
 
-    void expectEvent(Pattern expected) {
-        if (mExpectedEvents != null) mExpectedEvents.add(expected);
+    void expectEvent(String sequence, Pattern expected) {
+        if (mExpectedEvents != null) {
+            eventsListForSequence(sequence, mExpectedEvents).add(expected);
+        }
     }
 
+    // Returns non-null error message if the actual events in logcat don't match expected events.
+    // If we are not checking events, returns null.
     private String getEventMismatchMessage(boolean waitForExpectedCount) {
         if (mExpectedEvents == null) return null;
 
         try {
-            List<String> actual = getEvents();
+            Map<String, List<String>> actual = getEvents();
 
             if (waitForExpectedCount) {
                 // Wait until Launcher generates the expected number of events.
                 final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
                 while (SystemClock.uptimeMillis() < endTime
-                        && actual.size() < mExpectedEvents.size()) {
+                        && !receivedEnoughEvents(actual)) {
                     SystemClock.sleep(100);
                     actual = getEvents();
                 }
             }
 
-            for (int i = 0; i < mExpectedEvents.size(); ++i) {
-                if (i >= actual.size()) {
-                    return formatEventMismatchMessage("too few actual events", actual, i);
-                }
-                if (!mExpectedEvents.get(i).matcher(actual.get(i)).find()) {
-                    return formatEventMismatchMessage("a mismatched event", actual, i);
-                }
-            }
-
-            if (actual.size() > mExpectedEvents.size()) {
-                return formatEventMismatchMessage(
-                        "too many actual events", actual, mExpectedEvents.size());
-            }
+            return getEventMismatchErrorMessage(actual);
         } finally {
             stopRecordingEvents();
         }
-
-        return null;
     }
 
-    private String formatEventList(List events, int position) {
+    // Returns whether there is a sufficient number of events in the logcat to match the expected
+    // events.
+    private boolean receivedEnoughEvents(Map<String, List<String>> actual) {
+        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
+            final List<String> actualEventSequence = actual.get(expectedNamedSequence.getKey());
+            if (actualEventSequence == null
+                    || actualEventSequence.size() < expectedNamedSequence.getValue().size()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // If the list of actual events matches the list of expected events, returns -1, otherwise
+    // the position of the mismatch.
+    private static int getMismatchPosition(List<Pattern> expected, List<String> actual) {
+        for (int i = 0; i < expected.size(); ++i) {
+            if (i >= actual.size()
+                    || !expected.get(i).matcher(actual.get(i)).find()) {
+                return i;
+            }
+        }
+
+        if (actual.size() > expected.size()) return expected.size();
+
+        return -1;
+    }
+
+    // Returns non-null error message if the actual events passed as a param don't match expected
+    // events.
+    private String getEventMismatchErrorMessage(Map<String, List<String>> actualEvents) {
         final StringBuilder sb = new StringBuilder();
+
+        // Check that all expected even sequences match the actual data.
+        for (Map.Entry<String, List<Pattern>> expectedNamedSequence : mExpectedEvents.entrySet()) {
+            List<String> actualEventSequence = actualEvents.get(expectedNamedSequence.getKey());
+            if (actualEventSequence == null) actualEventSequence = new ArrayList<>();
+            final int mismatchPosition = getMismatchPosition(
+                    expectedNamedSequence.getValue(), actualEventSequence);
+            if (mismatchPosition != -1) {
+                formatSequenceWithMismatch(
+                        sb,
+                        expectedNamedSequence.getKey(),
+                        expectedNamedSequence.getValue(),
+                        actualEventSequence,
+                        mismatchPosition);
+            }
+        }
+
+        // Check for unexpected event sequences in the actual data.
+        for (Map.Entry<String, List<String>> actualNamedSequence : actualEvents.entrySet()) {
+            if (!mExpectedEvents.containsKey(actualNamedSequence.getKey())) {
+                formatSequenceWithMismatch(
+                        sb,
+                        actualNamedSequence.getKey(),
+                        new ArrayList<>(),
+                        actualNamedSequence.getValue(),
+                        0);
+            }
+        }
+
+        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+    }
+
+    private static void formatSequenceWithMismatch(
+            StringBuilder sb,
+            String sequenceName,
+            List<Pattern> expected,
+            List<String> actualEvents,
+            int mismatchPosition) {
+        sb.append("\n>> Sequence " + sequenceName);
+        sb.append("\n  Expected:");
+        formatEventListWithMismatch(sb, expected, mismatchPosition);
+        sb.append("\n  Actual:");
+        formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
+    }
+
+    private static void formatEventListWithMismatch(StringBuilder sb, List events, int position) {
         for (int i = 0; i < events.size(); ++i) {
-            sb.append("\n| ");
+            sb.append("\n  | ");
             sb.append(i == position ? "---> " : "     ");
             sb.append(events.get(i).toString());
         }
-        if (position == events.size()) sb.append("\n| ---> (end)");
-        return sb.toString();
-    }
-
-    private String formatEventMismatchMessage(String message, List<String> actual, int position) {
-        return message + ":"
-                + "\nExpected:" + formatEventList(mExpectedEvents, position)
-                + "\nActual:" + formatEventList(actual, position);
+        if (position == events.size()) sb.append("\n  | ---> (end)");
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 410e5a1..f955cf2 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -22,6 +22,8 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
 import java.util.regex.Pattern;
 
 /**
@@ -76,7 +78,7 @@
                         event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
                         () -> "Launching task didn't open a new window: "
                                 + mTask.getParent().getContentDescription());
-                mLauncher.expectEvent(TASK_START_EVENT);
+                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, TASK_START_EVENT);
             }
             return new Background(mLauncher);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index a0d5443..3f5dc8d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -260,8 +260,8 @@
     public Widgets openAllWidgets() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             verifyActiveContainer();
-            mLauncher.expectEvent(EVENT_CTRL_W_DOWN);
-            mLauncher.expectEvent(EVENT_CTRL_W_UP);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_DOWN);
+            mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_CTRL_W_UP);
             mLauncher.getDevice().pressKeyCode(KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON);
             try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer("pressed Ctrl+W")) {
                 return new Widgets(mLauncher);