Merge "Don't show the Tasbar Pinning edu for 3 button nav users" into main
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
index b7e1092..2d4894c 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
@@ -49,7 +49,7 @@
             "persist.wm.debug.desktop_stashing", false);
     private final Launcher mLauncher;
 
-    private boolean mFreeformTasksVisible;
+    private int mVisibleFreeformTasksCount;
     private boolean mInOverviewState;
     private boolean mBackgroundStateEnabled;
     private boolean mGestureInProgress;
@@ -68,13 +68,13 @@
     public void registerSystemUiListener() {
         mDesktopTaskListener = new IDesktopTaskListener.Stub() {
             @Override
-            public void onVisibilityChanged(int displayId, boolean visible) {
+            public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
                 MAIN_EXECUTOR.execute(() -> {
                     if (displayId == mLauncher.getDisplayId()) {
                         if (DEBUG) {
-                            Log.d(TAG, "desktop visibility changed value=" + visible);
+                            Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount);
                         }
-                        setFreeformTasksVisible(visible);
+                        setVisibleFreeformTasksCount(visibleTasksCount);
                     }
                 });
             }
@@ -112,39 +112,53 @@
      * Whether freeform windows are visible in desktop mode.
      */
     public boolean areFreeformTasksVisible() {
+        boolean freeformTasksVisible = mVisibleFreeformTasksCount > 0;
         if (DEBUG) {
-            Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + mFreeformTasksVisible
+            Log.d(TAG, "areFreeformTasksVisible: freeformVisible=" + freeformTasksVisible
                     + " overview=" + mInOverviewState);
         }
-        return mFreeformTasksVisible && !mInOverviewState;
+        return freeformTasksVisible && !mInOverviewState;
     }
 
     /**
-     * Sets whether freeform windows are visible and updates launcher visibility based on that.
+     * Number of visible freeform windows in desktop mode.
      */
-    public void setFreeformTasksVisible(boolean freeformTasksVisible) {
+    public int getVisibleFreeformTasksCount() {
+        return mVisibleFreeformTasksCount;
+    }
+
+    /**
+     * Sets the number of freeform windows that are visible and updates launcher visibility based on
+     * it.
+     */
+    public void setVisibleFreeformTasksCount(int visibleTasksCount) {
         if (DEBUG) {
-            Log.d(TAG, "setFreeformTasksVisible: visible=" + freeformTasksVisible
-                    + " currentValue=" + mFreeformTasksVisible);
+            Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
+                    + " currentValue=" + mVisibleFreeformTasksCount);
         }
         if (!isDesktopModeSupported()) {
             return;
         }
 
-        if (freeformTasksVisible != mFreeformTasksVisible) {
-            mFreeformTasksVisible = freeformTasksVisible;
-            if (mFreeformTasksVisible) {
-                setLauncherViewsVisibility(View.INVISIBLE);
-                if (!mInOverviewState) {
-                    // When freeform is visible & we're not in overview, we want launcher to appear
-                    // paused, this ensures that taskbar displays.
-                    markLauncherPaused();
+        if (visibleTasksCount != mVisibleFreeformTasksCount) {
+            final boolean wasVisible = mVisibleFreeformTasksCount > 0;
+            final boolean isVisible = visibleTasksCount > 0;
+            mVisibleFreeformTasksCount = visibleTasksCount;
+
+            if (wasVisible != isVisible) {
+                if (mVisibleFreeformTasksCount > 0) {
+                    setLauncherViewsVisibility(View.INVISIBLE);
+                    if (!mInOverviewState) {
+                        // When freeform is visible & we're not in overview, we want launcher to
+                        // appear paused, this ensures that taskbar displays.
+                        markLauncherPaused();
+                    }
+                } else {
+                    setLauncherViewsVisibility(View.VISIBLE);
+                    // If freeform isn't visible ensure that launcher appears resumed to behave
+                    // normally.
+                    markLauncherResumed();
                 }
-            } else {
-                setLauncherViewsVisibility(View.VISIBLE);
-                // If freeform isn't visible ensure that launcher appears resumed to behave
-                // normally.
-                markLauncherResumed();
             }
         }
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 3fb7247..5819bb3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -46,6 +46,8 @@
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -66,6 +68,7 @@
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.quickstep.SystemUiProxy;
 import com.android.wm.shell.bubbles.IBubblesListener;
@@ -408,8 +411,7 @@
                         info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
                 mSelectedBubble.getView().updateDotVisibility(true /* animate */);
             }
-            mSystemUiProxy.showBubble(getSelectedBubbleKey(),
-                    getBubbleBarOffsetX(), getBubbleBarOffsetY());
+            mSystemUiProxy.showBubble(getSelectedBubbleKey(), getExpandedBubbleBarDisplayBounds());
         } else {
             Log.w(TAG, "Trying to show the selected bubble but it's null");
         }
@@ -577,12 +579,27 @@
         return mIconFactory.createBadgedIconBitmap(drawable).icon;
     }
 
-    private int getBubbleBarOffsetY() {
+    /**
+     * Get bounds of the bubble bar as if it would be expanded.
+     * Calculates the bounds instead of retrieving current view location as the view may be
+     * animating.
+     */
+    private Rect getExpandedBubbleBarDisplayBounds() {
+        Point displaySize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+        Rect currentBarBounds = mBarView.getBubbleBarBounds();
+        Rect location = new Rect();
+        // currentBarBounds is only useful for distance from left or right edge.
+        // It contains the current bounds, calculate the expanded bounds.
+        if (mBarView.isOnLeft()) {
+            location.left = currentBarBounds.left;
+            location.right = (int) (currentBarBounds.left + mBarView.expandedWidth());
+        } else {
+            location.left = (int) (currentBarBounds.right - mBarView.expandedWidth());
+            location.right = currentBarBounds.right;
+        }
         final int translation = (int) abs(mBubbleStashController.getBubbleBarTranslationY());
-        return translation + mBarView.getHeight();
-    }
-
-    private int getBubbleBarOffsetX() {
-        return mBarView.getWidth() + mBarView.getHorizontalMargin();
+        location.top = displaySize.y - mBarView.getHeight() - translation;
+        location.bottom = displaySize.y - translation;
+        return location;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index fbc7da1..8f693a6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -205,7 +205,10 @@
         mRelativePivotX = onLeft ? 0f : 1f;
     }
 
-    private boolean isOnLeft() {
+    /**
+     * @return <code>true</code> when bar is pinned to the left edge of the screen
+     */
+    public boolean isOnLeft() {
         return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
     }
 
@@ -509,7 +512,12 @@
         return mIsBarExpanded;
     }
 
-    private float expandedWidth() {
+    /**
+     * Get width of the bubble bar as if it would be expanded.
+     *
+     * @return width of the bubble bar in its expanded state, regardless of current width
+     */
+    public float expandedWidth() {
         final int childCount = getChildCount();
         final int horizontalPadding = getPaddingStart() + getPaddingEnd();
         return childCount * (mIconSize + mIconSpacing) + horizontalPadding;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
index c1a85fa..369ff14 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsUI.java
@@ -21,14 +21,14 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.LauncherPrefs.PRIVATE_SPACE_APPS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE;
+import static com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS;
 import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_HIGHLIGHT_KEY;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
 import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -50,6 +50,7 @@
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.util.ArrayMap;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -88,11 +89,14 @@
 
     private static final String ACTION_PLUGIN_SETTINGS =
             "com.android.systemui.action.PLUGIN_SETTINGS";
+    private static final String TAG = "DeveloperOptionsUI";
     private static final String PLUGIN_PERMISSION = "com.android.systemui.permission.PLUGIN";
 
     private final PreferenceFragmentCompat mFragment;
     private final PreferenceScreen mPreferenceScreen;
 
+    private final FlagTogglerPrefUi mFlagTogglerPrefUi;
+
     private PreferenceCategory mPluginsCategory;
 
     public DeveloperOptionsUI(PreferenceFragmentCompat fragment, PreferenceCategory flags) {
@@ -107,8 +111,9 @@
         parent.addView(topBar, parent.indexOfChild(listView));
         initSearch(topBar.findViewById(R.id.filter_box));
 
-        new FlagTogglerPrefUi(mFragment.requireActivity(), topBar.findViewById(R.id.flag_apply_btn))
-                .applyTo(flags);
+        mFlagTogglerPrefUi = new FlagTogglerPrefUi(mFragment.requireActivity(),
+                topBar.findViewById(R.id.flag_apply_btn));
+        mFlagTogglerPrefUi.applyTo(flags);
 
         loadPluginPrefs();
         maybeAddSandboxCategory();
@@ -350,23 +355,27 @@
     private void addCustomLpnhCategory() {
         PreferenceCategory category = newCategory("Long Press Nav Handle Config");
         if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
+            category.addPreference(createSeekBarPreference(
+                    "Slop multiplier (applied to edge slop, "
                             + "which is generally already 50% higher than touch slop)",
-                    25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
-            category.addPreference(createSeekBarPreference("Trigger milliseconds",
-                    100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
+                    25, 200, 100, LPNH_SLOP_PERCENTAGE));
+            category.addPreference(createSeekBarPreference("LPNH timeout",
+                    100, 500, 1, LPNH_TIMEOUT_MS));
         }
         if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get()) {
-            category.addPreference(createSeekBarPreference("Haptic hint start scale",
-                    0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT));
+            category.addPreference(
+                    createSeekBarPreference("Haptic hint start scale",
+                            0, 100, 100, LPNH_HAPTIC_HINT_START_SCALE_PERCENT));
             category.addPreference(createSeekBarPreference("Haptic hint end scale",
-                    0, 100, 100, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT));
-            category.addPreference(createSeekBarPreference("Haptic hint scale exponent",
-                    1, 5, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT));
-            category.addPreference(createSeekBarPreference("Haptic hint iterations (12 ms each)",
-                    0, 200, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS));
+                    0, 100, 100, LPNH_HAPTIC_HINT_END_SCALE_PERCENT));
+            category.addPreference(
+                    createSeekBarPreference("Haptic hint scale exponent",
+                            1, 5, 1, LPNH_HAPTIC_HINT_SCALE_EXPONENT));
+            category.addPreference(
+                    createSeekBarPreference("Haptic hint iterations (12 ms each)",
+                            0, 200, 1, LPNH_HAPTIC_HINT_ITERATIONS));
             category.addPreference(createSeekBarPreference("Haptic hint delay (ms)",
-                    0, 400, 1, LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY));
+                    0, 400, 1, LPNH_HAPTIC_HINT_DELAY));
         }
     }
 
@@ -376,6 +385,29 @@
                 "Number of Apps to put in private region", 0, 100, 1, PRIVATE_SPACE_APPS));
     }
 
+    private SeekBarPreference createSeekBarPreference(String title, int min,
+            int max, int scale, FeatureFlags.IntFlag flag) {
+        if (!(flag instanceof IntDebugFlag)) {
+            Log.e(TAG, "Cannot create seekbar preference with IntFlag. Use a launcher preference "
+                    + "flag or pref-backed IntDebugFlag instead");
+            return null;
+        }
+        IntDebugFlag debugflag = (IntDebugFlag) flag;
+        if (debugflag.launcherPrefFlag == null) {
+            Log.e(TAG, "Cannot create seekbar preference with IntDebugFlag. Use a launcher "
+                    + "preference flag or pref-backed IntDebugFlag instead");
+            return null;
+        }
+        SeekBarPreference seekBarPref = createSeekBarPreference(title, min, max, scale,
+                debugflag.launcherPrefFlag);
+        int value = flag.get();
+        seekBarPref.setValue(value);
+        // For some reason the initial value is not triggering the summary update, so call manually.
+        seekBarPref.setSummary(String.valueOf(scale == 1 ? value
+                : value / (float) scale));
+        return seekBarPref;
+    }
+
     /**
      * Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
      *
@@ -401,6 +433,7 @@
             LauncherPrefs.get(getContext()).put(launcherPref, newValue);
             preference.setSummary(String.valueOf(scale == 1 ? newValue
                     : (int) newValue / (float) scale));
+            mFlagTogglerPrefUi.updateMenu();
             return true;
         });
         int value = LauncherPrefs.get(getContext()).get(launcherPref);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
index ec0566a..4326c67 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagTogglerPrefUi.java
@@ -34,6 +34,7 @@
 import androidx.preference.PreferenceViewHolder;
 import androidx.preference.SwitchPreference;
 
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 
@@ -144,7 +145,7 @@
         switchPreference.setSummary(Html.fromHtml(summary + flag.description));
     }
 
-    private void updateMenu() {
+    public void updateMenu() {
         mFlagsApplyButton.setVisibility(anyChanged() ? View.VISIBLE : View.INVISIBLE);
     }
 
@@ -161,12 +162,22 @@
         return mDataStore.getBoolean(flag.key, defaultValue);
     }
 
+    private int getIntFlagStateFromSharedPrefs(IntDebugFlag flag) {
+        LauncherPrefs prefs = LauncherPrefs.get(mContext);
+        return flag.launcherPrefFlag == null ? flag.get() : prefs.get(flag.launcherPrefFlag);
+    }
+
     private boolean anyChanged() {
         for (DebugFlag flag : FlagsFactory.getDebugFlags()) {
             if (getFlagStateFromSharedPrefs(flag) != flag.get()) {
                 return true;
             }
         }
+        for (IntDebugFlag flag : FlagsFactory.getIntDebugFlags()) {
+            if (getIntFlagStateFromSharedPrefs(flag) != flag.get()) {
+                return true;
+            }
+        }
         return false;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index 48d313e..686ed64 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -23,6 +23,8 @@
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import static java.util.Collections.unmodifiableList;
+
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.provider.DeviceConfig;
@@ -30,7 +32,10 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
+import com.android.launcher3.ConstantItem;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
 import com.android.launcher3.config.FeatureFlags.FlagState;
 import com.android.launcher3.config.FeatureFlags.IntFlag;
@@ -57,7 +62,7 @@
     public static final String NAMESPACE_LAUNCHER = "launcher";
 
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
-    private static final List<IntFlag> sIntFlags = new ArrayList<>();
+    private static final List<IntDebugFlag> sIntDebugFlags = new ArrayList<>();
     private static SharedPreferences sSharedPreferences;
 
     static final BooleanFlag TEAMFOOD_FLAG = getReleaseFlag(
@@ -132,11 +137,32 @@
      */
     public static IntFlag getIntFlag(
             int bugId, String key, int defaultValueInCode, String description) {
+        return getIntFlag(bugId, key, defaultValueInCode, description, null);
+    }
+
+    /**
+     * Creates a new integer flag.
+     *
+     * @param launcherPrefFlag Set launcherPrefFlag to non-null if you want
+     * to modify the int flag in Launcher Developer Options and IntDebugFlag
+     * will be backed up by LauncherPrefs. Modified int value will be saved
+     * in LauncherPrefs.
+     */
+    public static IntFlag getIntFlag(
+            int bugId, String key, int defaultValueInCode, String description,
+            @Nullable ConstantItem<Integer> launcherPrefFlag) {
         INSTANCE.mKeySet.add(key);
         int defaultValue = DeviceConfig.getInt(NAMESPACE_LAUNCHER, key, defaultValueInCode);
         if (IS_DEBUG_DEVICE) {
-            IntDeviceFlag flag = new IntDeviceFlag(key, defaultValue, defaultValueInCode);
-            sIntFlags.add(flag);
+            int currentValue;
+            if (launcherPrefFlag == null) {
+                currentValue = defaultValue;
+            } else {
+                currentValue = LauncherPrefs.get(currentApplication()).get(launcherPrefFlag);
+            }
+            IntDebugFlag flag = new IntDebugFlag(key, currentValue, defaultValueInCode,
+                    launcherPrefFlag);
+            sIntDebugFlags.add(flag);
             return flag;
         } else {
             return new IntFlag(defaultValue);
@@ -152,6 +178,15 @@
         }
     }
 
+    static List<IntDebugFlag> getIntDebugFlags() {
+        if (!IS_DEBUG_DEVICE) {
+            return unmodifiableList(Collections.emptyList());
+        }
+        synchronized (sIntDebugFlags) {
+            return unmodifiableList(sIntDebugFlags);
+        }
+    }
+
     /** Returns the SharedPreferences instance backing Debug FeatureFlags. */
     @NonNull
     static SharedPreferences getSharedPreferences() {
@@ -180,8 +215,8 @@
             }
         }
         pw.println("  IntFlags:");
-        synchronized (sIntFlags) {
-            for (IntFlag flag : sIntFlags) {
+        synchronized (sIntDebugFlags) {
+            for (IntFlag flag : sIntDebugFlags) {
                 pw.println("    " + flag);
             }
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
similarity index 73%
rename from quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
rename to quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
index 4f3b0ae..1350aa8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/IntDeviceFlag.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/IntDebugFlag.java
@@ -15,16 +15,23 @@
  */
 package com.android.launcher3.uioverrides.flags;
 
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.config.FeatureFlags.IntFlag;
 
-public class IntDeviceFlag extends IntFlag {
+public class IntDebugFlag extends IntFlag {
     public final String key;
     private final int mDefaultValueInCode;
+    @Nullable
+    public final ConstantItem<Integer> launcherPrefFlag;
 
-    public IntDeviceFlag(String key, int currentValue, int defaultValueInCode) {
+    public IntDebugFlag(String key, int currentValue, int defaultValueInCode,
+            @Nullable ConstantItem<Integer> launcherPrefFlag) {
         super(currentValue);
         this.key = key;
         mDefaultValueInCode = defaultValueInCode;
+        this.launcherPrefFlag = launcherPrefFlag;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 179612b..d617828 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -66,7 +65,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
@@ -608,7 +606,7 @@
 
         if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
             float customSlopMultiplier =
-                    LauncherPrefs.get(mContext).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
+                    FeatureFlags.LPNH_SLOP_PERCENTAGE.get() / 100f;
             return customSlopMultiplier * slopMultiplier * touchSlop;
         } else {
             return slopMultiplier * touchSlop;
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 2e70703..6a9caf7 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -29,6 +29,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.TaskViewSimulator;
@@ -68,13 +69,16 @@
      */
     public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
         if (isDesktopModeSupported()) {
-            // TODO(279931899): binder call, only for prototyping. Creating the gluer should be
-            //  postponed so we can create it when we have the remote animation targets ready.
-            int desktopTasks = SystemUiProxy.INSTANCE.get(context).getVisibleDesktopTaskCount(
-                    context.getDisplayId());
-            if (desktopTasks > 0) {
-                init(context, sizingStrategy, desktopTasks, true /* forDesktop */);
-                return;
+            DesktopVisibilityController desktopVisibilityController =
+                    LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
+            if (desktopVisibilityController != null) {
+                int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
+                if (visibleTasksCount > 0) {
+                    // Allocate +1 to account for a new task added to the desktop mode
+                    int numHandles = visibleTasksCount + 1;
+                    init(context, sizingStrategy, numHandles, true /* forDesktop */);
+                    return;
+                }
             }
         }
 
@@ -135,18 +139,7 @@
      * the left/top task, index 1 right/bottom.
      */
     public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
-        // Resize the mRemoteTargetHandles array since we started assuming split screen, but
-        // targets.apps is the ultimate source of truth here
-        long appCount = Arrays.stream(targets.apps)
-                .filter(app -> app.mode == targets.targetMode)
-                .count();
-        Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
-        if (appCount < mRemoteTargetHandles.length) {
-            Log.d(TAG, "resizing handles");
-            RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
-            System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
-            mRemoteTargetHandles = newHandles;
-        }
+        resizeRemoteTargetHandles(targets);
 
         // If we are in a true split screen case (2 apps running on screen), either:
         //     a) mSplitBounds was already set (from the clicked GroupedTaskView)
@@ -228,6 +221,8 @@
      * transform params per app in {@code targets.apps} list.
      */
     public RemoteTargetHandle[] assignTargetsForDesktop(RemoteAnimationTargets targets) {
+        resizeRemoteTargetHandles(targets);
+
         for (int i = 0; i < mRemoteTargetHandles.length; i++) {
             RemoteAnimationTarget primaryTaskTarget = targets.apps[i];
             mRemoteTargetHandles[i].mTransformParams.setTargetSet(
@@ -237,6 +232,23 @@
         return mRemoteTargetHandles;
     }
 
+    /**
+     * Resize the `mRemoteTargetHandles` array since we assumed initial size, but
+     * `targets.apps` is the ultimate source of truth here
+     */
+    private void resizeRemoteTargetHandles(RemoteAnimationTargets targets) {
+        long appCount = Arrays.stream(targets.apps)
+                .filter(app -> app.mode == targets.targetMode)
+                .count();
+        Log.d(TAG, "appCount: " + appCount + " handleLength: " + mRemoteTargetHandles.length);
+        if (appCount < mRemoteTargetHandles.length) {
+            Log.d(TAG, "resizing handles");
+            RemoteTargetHandle[] newHandles = new RemoteTargetHandle[(int) appCount];
+            System.arraycopy(mRemoteTargetHandles, 0/*src*/, newHandles, 0/*dst*/, (int) appCount);
+            mRemoteTargetHandles = newHandles;
+        }
+    }
+
     private Rect getStartBounds(RemoteAnimationTarget target) {
         return target.startBounds == null ? target.screenSpaceBounds : target.startBounds;
     }
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 56a4024..52f9d8d 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -731,15 +731,12 @@
     /**
      * Tells SysUI to show the bubble with the provided key.
      * @param key the key of the bubble to show.
-     * @param bubbleBarOffsetX the offset of the bubble bar from the edge of the screen on the X
-     *                         axis.
-     * @param bubbleBarOffsetY the offset of the bubble bar from the edge of the screen on the Y
-     *                         axis.
+     * @param bubbleBarBounds bounds of the bubble bar in display coordinates
      */
-    public void showBubble(String key, int bubbleBarOffsetX, int bubbleBarOffsetY) {
+    public void showBubble(String key, Rect bubbleBarBounds) {
         if (mBubbles != null) {
             try {
-                mBubbles.showBubble(key, bubbleBarOffsetX, bubbleBarOffsetY);
+                mBubbles.showBubble(key, bubbleBarBounds);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call showBubble");
             }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 4c0b550..cf8750f 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_NAVBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_STASHED_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
@@ -26,7 +25,6 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.util.DisplayController;
@@ -63,7 +61,7 @@
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
         mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
         if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
-            mLongPressTimeout = LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
+            mLongPressTimeout = FeatureFlags.LPNH_TIMEOUT_MS.get();
         } else {
             mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
         }
diff --git a/res/layout/widgets_two_pane_sheet_paged_view.xml b/res/layout/widgets_two_pane_sheet_paged_view.xml
index 442957a..4a7749b 100644
--- a/res/layout/widgets_two_pane_sheet_paged_view.xml
+++ b/res/layout/widgets_two_pane_sheet_paged_view.xml
@@ -66,7 +66,7 @@
                 <include layout="@layout/widgets_search_bar" />
             </FrameLayout>
 
-            <LinearLayout
+            <FrameLayout
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:id="@+id/suggestions_header"
@@ -74,7 +74,7 @@
                 android:orientation="horizontal"
                 android:background="?attr/widgetPickerPrimarySurfaceColor"
                 launcher:layout_sticky="true">
-            </LinearLayout>
+            </FrameLayout>
 
             <com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip
                 android:id="@+id/tabs"
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index ba65bea..baa1ee3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -19,7 +19,6 @@
 import static android.text.Layout.Alignment.ALIGN_NORMAL;
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE;
@@ -556,9 +555,6 @@
     }
 
     private void checkForEllipsis() {
-        if (!ENABLE_ICON_LABEL_AUTO_SCALING.get()) {
-            return;
-        }
         float width = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
         if (width <= 0) {
             return;
@@ -830,7 +826,6 @@
         float currentWordWidth, runningWidth = 0;
         CharSequence currentWord;
         StringBuilder newString = new StringBuilder();
-        // TODO: Remove when ENABLE_ICON_LABEL_AUTO_SCALING feature flag is being cleaned up.
         paint.setLetterSpacing(MIN_LETTER_SPACING);
         int stringPtr = 0;
         for (int i = 0; i < breakPoints.size()+1; i++) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 1b4edf8..941a793 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.LauncherState.EDIT_MODE;
 import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
@@ -214,6 +213,7 @@
     // Related to accessible drag and drop
     DragAndDropAccessibilityDelegate mTouchHelper;
 
+    CellLayoutContainer mCellLayoutContainer;
 
     public static final FloatProperty<CellLayout> SPRING_LOADED_PROGRESS =
             new FloatProperty<CellLayout>("spring_loaded_progress") {
@@ -228,8 +228,9 @@
                 }
             };
 
-    public CellLayout(Context context) {
-        this(context, null);
+    public CellLayout(Context context, CellLayoutContainer container) {
+        this(context, (AttributeSet) null);
+        this.mCellLayoutContainer = container;
     }
 
     public CellLayout(Context context, AttributeSet attrs) {
@@ -316,6 +317,14 @@
         addView(mShortcutsAndWidgets);
     }
 
+    public CellLayoutContainer getCellLayoutContainer() {
+        return mCellLayoutContainer;
+    }
+
+    public void setCellLayoutContainer(CellLayoutContainer cellLayoutContainer) {
+        mCellLayoutContainer = cellLayoutContainer;
+    }
+
     /**
      * Sets or clears a delegate used for accessible drag and drop
      */
@@ -573,9 +582,7 @@
     }
 
     protected void updateBgAlpha() {
-        if (!getWorkspace().mLauncher.isInState(EDIT_MODE)) {
-            mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
-        }
+        mBackground.setAlpha((int) (mSpringLoadedProgress * 255));
     }
 
     /**
@@ -1187,7 +1194,7 @@
         // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
         View view = dragObject.dragView.getContentView();
         if (view instanceof LauncherAppWidgetHostView) {
-            int screenId = getWorkspace().getIdForScreen(this);
+            int screenId = mCellLayoutContainer.getCellLayoutId(this);
             cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
 
             ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
@@ -1200,25 +1207,19 @@
             return getContext().getString(R.string.move_to_hotseat_position,
                     Math.max(cellX, cellY) + 1);
         } else {
-            Workspace<?> workspace = getWorkspace();
             int row = cellY + 1;
-            int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
-            int panelCount = workspace.getPanelCount();
-            int screenId = workspace.getIdForScreen(this);
-            int pageIndex = workspace.getPageIndexForScreenId(screenId);
+            int col = Utilities.isRtl(getResources()) ? mCountX - cellX : cellX + 1;
+            int panelCount = mCellLayoutContainer.getPanelCount();
+            int pageIndex = mCellLayoutContainer.getCellLayoutIndex(this);
             if (panelCount > 1) {
                 // Increment the column if the target is on the right side of a two panel home
                 col += (pageIndex % panelCount) * mCountX;
             }
             return getContext().getString(R.string.move_to_empty_cell_description, row, col,
-                    workspace.getPageDescription(pageIndex));
+                    mCellLayoutContainer.getPageDescription(pageIndex));
         }
     }
 
-    private Workspace<?> getWorkspace() {
-        return Launcher.cast(mActivity).getWorkspace();
-    }
-
     public void clearDragOutlines() {
         final int oldIndex = mDragOutlineCurrent;
         mDragOutlineAnims[oldIndex].animateOut();
@@ -1452,7 +1453,7 @@
     private void commitTempPlacement(View dragView) {
         mTmpOccupied.copyTo(mOccupied);
 
-        int screenId = getWorkspace().getIdForScreen(this);
+        int screenId = mCellLayoutContainer.getCellLayoutId(this);
         int container = Favorites.CONTAINER_DESKTOP;
 
         if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/CellLayoutContainer.java b/src/com/android/launcher3/CellLayoutContainer.java
new file mode 100644
index 0000000..9ee0f70
--- /dev/null
+++ b/src/com/android/launcher3/CellLayoutContainer.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 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;
+
+/**
+ * This interface should be implemented for any container/view that has a CellLayout as a children.
+ */
+public interface CellLayoutContainer {
+
+    /**
+     * Get the CellLayoutId for the given cellLayout.
+     */
+    int getCellLayoutId(CellLayout cellLayout);
+
+    /**
+     * Get the index of the given CellLayout out of all the other CellLayouts.
+     */
+    int getCellLayoutIndex(CellLayout cellLayout);
+
+    /**
+     * The total number of CellLayouts in the container.
+     */
+    int getPanelCount();
+
+    /**
+     * Used for accessibility, it returns the string that the assistant is going to say when
+     * referring to the given CellLayout.
+     */
+    String getPageDescription(int pageIndex);
+}
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 3b12b86..37737d8 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -207,6 +207,7 @@
 
     public void setWorkspace(Workspace<?> w) {
         mWorkspace = w;
+        setCellLayoutContainer(w);
     }
 
     @Override
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 44a1bf0..496cb4e 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -99,7 +99,6 @@
 import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -132,6 +131,7 @@
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
+import android.util.Pair;
 import android.util.SparseArray;
 import android.view.KeyEvent;
 import android.view.KeyboardShortcutGroup;
@@ -264,7 +264,6 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -522,7 +521,7 @@
         mAppWidgetHolder.startListening();
         mAppWidgetHolder.addProviderChangeListener(() -> refreshAndBindWidgetsForPackageUser(null));
         mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
-                mFocusHandler, new CellLayout(mWorkspace.getContext()));
+                mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
 
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
@@ -2125,30 +2124,23 @@
      */
     @Override
     public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
-        bindItems(items, forceAnimateIcons, /* focusFirstItemForAccessibility= */ false);
+        bindItems(items.stream().map(i -> Pair.create(
+                i, getItemInflater().inflateItem(i, getModelWriter()))).toList(),
+                forceAnimateIcons ? new AnimatorSet() : null);
     }
 
-
     /**
-     * Bind the items start-end from the list.
+     * Bind all the items in the map, ignoring any null views
      *
-     * Implementation of the method from LauncherModel.Callbacks.
-     *
-     * @param focusFirstItemForAccessibility true iff the first item to be added to the workspace
-     *                                       should be focused for accessibility.
+     * @param boundAnim if non-null, uses it to create and play the bounce animation for added views
      */
-    public void bindItems(
-            final List<ItemInfo> items,
-            final boolean forceAnimateIcons,
-            final boolean focusFirstItemForAccessibility) {
+    public void bindItems(List<Pair<ItemInfo, View>> shortcuts, @Nullable AnimatorSet boundAnim) {
         // Get the list of added items and intersect them with the set of items here
-        final Collection<Animator> bounceAnims = new ArrayList<>();
         Workspace<?> workspace = mWorkspace;
         int newItemsScreenId = -1;
-        int end = items.size();
-        View newView = null;
-        for (int i = 0; i < end; i++) {
-            final ItemInfo item = items.get(i);
+        int index = 0;
+        for (Pair<ItemInfo, View> e : shortcuts) {
+            final ItemInfo item = e.first;
 
             // Remove colliding items.
             CellPos presenterPos = getCellPosMapper().mapModelToPresenter(item);
@@ -2167,42 +2159,26 @@
                 }
             }
 
-            final View view = mItemInflater.inflateItem(item, getModelWriter());
+            final View view = e.second;
             if (view == null) {
                 continue;
             }
             workspace.addInScreenFromBind(view, item);
-            if (forceAnimateIcons) {
+            if (boundAnim != null) {
                 // Animate all the applications up now
                 view.setAlpha(0f);
                 view.setScaleX(0f);
                 view.setScaleY(0f);
-                bounceAnims.add(createNewAppBounceAnimation(view, i));
+                boundAnim.play(createNewAppBounceAnimation(view, index++));
                 newItemsScreenId = presenterPos.screenId;
             }
-
-            if (newView == null) {
-                newView = view;
-            }
         }
 
-        View viewToFocus = newView;
         // Animate to the correct page
-        if (forceAnimateIcons && newItemsScreenId > -1) {
-            AnimatorSet anim = new AnimatorSet();
-            anim.playTogether(bounceAnims);
-            if (focusFirstItemForAccessibility && viewToFocus != null) {
-                anim.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-                    }
-                });
-            }
-
+        if (boundAnim != null && newItemsScreenId > -1) {
             int currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
             final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
-            final Runnable startBounceAnimRunnable = anim::start;
+            final Runnable startBounceAnimRunnable = boundAnim::start;
 
             if (canAnimatePageChange() && newItemsScreenId != currentScreenId) {
                 // We post the animation slightly delayed to prevent slowdowns
@@ -2215,8 +2191,6 @@
             } else {
                 mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
             }
-        } else if (focusFirstItemForAccessibility && viewToFocus != null) {
-            viewToFocus.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         }
         workspace.requestLayout();
     }
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 067d150..3b62ae1 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,17 +20,11 @@
 import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener
 import android.util.Log
+import android.view.ViewConfiguration
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT
-import com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT
-import com.android.launcher3.config.FeatureFlags.LPNH_SLOP_PERCENTAGE
-import com.android.launcher3.config.FeatureFlags.LPNH_TIMEOUT_MS
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
@@ -313,53 +307,45 @@
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
-            nonRestorableItem(
-                "pref_long_press_nav_handle_slop_percentage",
-                LPNH_SLOP_PERCENTAGE.get(),
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
+            nonRestorableItem("LPNH_SLOP_PERCENTAGE", 100, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField
         val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
             nonRestorableItem(
-                "pref_long_press_nav_handle_timeout_ms",
-                LPNH_TIMEOUT_MS.get(),
+                "LPNH_TIMEOUT_MS",
+                ViewConfiguration.getLongPressTimeout(),
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT =
             nonRestorableItem(
-                "pref_long_press_nav_handle_haptic_hint_start_scale_percent",
-                LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get(),
+                "LPNH_HAPTIC_HINT_START_SCALE_PERCENT",
+                0,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT =
             nonRestorableItem(
-                "pref_long_press_nav_handle_haptic_hint_end_scale_percent",
-                LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get(),
+                "LPNH_HAPTIC_HINT_END_SCALE_PERCENT",
+                100,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT =
             nonRestorableItem(
-                "pref_long_press_nav_handle_haptic_hint_scale_exponent",
-                LPNH_HAPTIC_HINT_SCALE_EXPONENT.get(),
+                "LPNH_HAPTIC_HINT_SCALE_EXPONENT",
+                1,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS =
             nonRestorableItem(
-                "pref_long_press_nav_handle_haptic_hint_iterations",
-                LPNH_HAPTIC_HINT_ITERATIONS.get(),
+                "LPNH_HAPTIC_HINT_ITERATIONS",
+                50,
                 EncryptionType.MOVE_TO_DEVICE_PROTECTED
             )
         @JvmField
         val LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY =
-            nonRestorableItem(
-                "pref_long_press_nav_handle_haptic_hint_delay",
-                LPNH_HAPTIC_HINT_DELAY.get(),
-                EncryptionType.MOVE_TO_DEVICE_PROTECTED
-            )
+            nonRestorableItem("LPNH_HAPTIC_HINT_DELAY", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
         @JvmField
         val PRIVATE_SPACE_APPS =
             nonRestorableItem("pref_private_space_apps", 0, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2eff8aa..984a9ae 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -143,7 +143,7 @@
  * @param <T> Class that extends View and PageIndicator
  */
 public class Workspace<T extends View & PageIndicator> extends PagedView<T>
-        implements DropTarget, DragSource, View.OnTouchListener,
+        implements DropTarget, DragSource, View.OnTouchListener, CellLayoutContainer,
         DragController.DragListener, Insettable, StateHandler<LauncherState>,
         WorkspaceLayoutManager, LauncherBindableItemsContainer, LauncherOverlayCallbacks {
 
@@ -511,11 +511,6 @@
         return !FOLDABLE_SINGLE_PAGE.get() && mLauncher.mDeviceProfile.isTwoPanels;
     }
 
-    @Override
-    public int getPanelCount() {
-        return isTwoPanelEnabled() ? 2 : super.getPanelCount();
-    }
-
     public void deferRemoveExtraEmptyScreen() {
         mDeferRemoveExtraEmptyScreen = true;
     }
@@ -683,6 +678,7 @@
             newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                     R.layout.workspace_screen, this, false /* attachToRoot */);
         }
+        newScreen.setCellLayoutContainer(this);
 
         mWorkspaceScreens.put(screenId, newScreen);
         mScreenOrder.add(insertIndex, screenId);
@@ -949,7 +945,8 @@
         return mWorkspaceScreens.get(screenId);
     }
 
-    public int getIdForScreen(CellLayout layout) {
+    @Override
+    public int getCellLayoutId(CellLayout layout) {
         int index = mWorkspaceScreens.indexOfValue(layout);
         if (index != -1) {
             return mWorkspaceScreens.keyAt(index);
@@ -961,6 +958,16 @@
         return indexOfChild(mWorkspaceScreens.get(screenId));
     }
 
+    @Override
+    public int getCellLayoutIndex(CellLayout cellLayout) {
+        return indexOfChild(mWorkspaceScreens.get(getCellLayoutId(cellLayout)));
+    }
+
+    @Override
+    public int getPanelCount() {
+        return isTwoPanelEnabled() ? 2 : super.getPanelCount();
+    }
+
     public IntSet getCurrentPageScreenIds() {
         return IntSet.wrap(getScreenIdForPageIndex(getCurrentPage()));
     }
@@ -1001,7 +1008,7 @@
         if (!isTwoPanelEnabled()) {
             return null;
         }
-        int screenId = getIdForScreen(cellLayout);
+        int screenId = getCellLayoutId(cellLayout);
         if (screenId == -1) {
             return null;
         }
@@ -1826,7 +1833,7 @@
             }
         }
 
-        int screenId = getIdForScreen(dropTargetLayout);
+        int screenId = getCellLayoutId(dropTargetLayout);
         if (Workspace.EXTRA_EMPTY_SCREEN_IDS.contains(screenId)) {
             commitExtraEmptyScreens();
         }
@@ -1909,7 +1916,7 @@
 
         if (v == null || hasntMoved || !mCreateUserFolderOnDrop) return false;
         mCreateUserFolderOnDrop = false;
-        final int screenId = getIdForScreen(target);
+        final int screenId = getCellLayoutId(target);
 
         boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
         boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
@@ -2010,7 +2017,7 @@
                         LauncherSettings.Favorites.CONTAINER_HOTSEAT :
                         LauncherSettings.Favorites.CONTAINER_DESKTOP;
                 int screenId = (mTargetCell[0] < 0) ?
-                        mDragInfo.screenId : getIdForScreen(dropTargetLayout);
+                        mDragInfo.screenId : getCellLayoutId(dropTargetLayout);
                 int spanX = mDragInfo != null ? mDragInfo.spanX : 1;
                 int spanY = mDragInfo != null ? mDragInfo.spanY : 1;
                 // First we find the cell nearest to point at which the item is
@@ -2766,7 +2773,7 @@
         final int container = mLauncher.isHotseatLayout(cellLayout)
                 ? LauncherSettings.Favorites.CONTAINER_HOTSEAT
                 : LauncherSettings.Favorites.CONTAINER_DESKTOP;
-        final int screenId = getIdForScreen(cellLayout);
+        final int screenId = getCellLayoutId(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
                 && !mLauncher.isInState(SPRING_LOADED)
@@ -3480,14 +3487,15 @@
 
     @Override
     protected String getCurrentPageDescription() {
-        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
-        return getPageDescription(page);
+        int pageIndex = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        return getPageDescription(pageIndex);
     }
 
     /**
      * @param page page index.
      * @return Description of the page at the given page index.
      */
+    @Override
     public String getPageDescription(int page) {
         int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0844275..e65e614 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -5,18 +5,22 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
 
+import android.animation.AnimatorSet;
 import android.appwidget.AppWidgetProviderInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Handler;
 import android.util.Log;
+import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ButtonDropTarget;
@@ -396,10 +400,7 @@
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
 
-                mContext.bindItems(
-                        Collections.singletonList(info),
-                        /* forceAnimateIcons= */ true,
-                        /* focusFirstItemForAccessibility= */ accessibility);
+                bindItem(item, accessibility);
                 announceConfirmation(R.string.item_added_to_workspace);
             } else if (item instanceof PendingAddItemInfo) {
                 PendingAddItemInfo info = (PendingAddItemInfo) item;
@@ -412,7 +413,7 @@
                 mContext.getModelWriter().addItemToDatabase(info,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP,
                         screenId, coordinates[0], coordinates[1]);
-                mContext.bindItems(Collections.singletonList(info), true, accessibility);
+                bindItem(info, accessibility);
             } else if (item instanceof FolderInfo fi) {
                 mContext.getModelWriter().addItemToDatabase(fi,
                         LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, coordinates[0],
@@ -420,11 +421,26 @@
                 fi.contents.forEach(member -> {
                     mContext.getModelWriter().addItemToDatabase(member, fi.id, -1, -1, -1);
                 });
-                mContext.bindItems(Collections.singletonList(fi), true, accessibility);
+                bindItem(fi, accessibility);
             }
         }));
         return true;
     }
+
+    private void bindItem(ItemInfo item, boolean focusForAccessibility) {
+        View view = mContext.getItemInflater().inflateItem(item, mContext.getModelWriter());
+        if (view == null) {
+            return;
+        }
+        AnimatorSet anim = null;
+        if (focusForAccessibility) {
+            anim = new AnimatorSet();
+            anim.addListener(forEndCallback(
+                    () -> view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
+        }
+        mContext.bindItems(Collections.singletonList(Pair.create(item, view)), anim);
+    }
+
     /**
      * Functionality to move the item {@link ItemInfo} to the workspace
      * @param item item to be moved
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 1994bd1..40c368b 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -17,11 +17,17 @@
 package com.android.launcher3.config;
 
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
+import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
 import static com.android.launcher3.config.FeatureFlags.FlagState.DISABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 import static com.android.launcher3.config.FeatureFlags.FlagState.TEAMFOOD;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getDebugFlag;
-import static com.android.launcher3.uioverrides.flags.FlagsFactory.getIntFlag;
 import static com.android.launcher3.uioverrides.flags.FlagsFactory.getReleaseFlag;
 import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
 
@@ -31,6 +37,7 @@
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Flags;
+import com.android.launcher3.uioverrides.flags.FlagsFactory;
 
 import java.util.function.Predicate;
 import java.util.function.ToIntFunction;
@@ -93,6 +100,12 @@
             "ENABLE_DISMISS_PREDICTION_UNDO", DISABLED,
             "Show an 'Undo' snackbar when users dismiss a predicted hotseat item");
 
+    public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
+            251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
+            "Marks LauncherPref data as (and allows it to) available while the device is"
+                    + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
+                    + " data. Improves startup latency.");
+
     public static final BooleanFlag CONTINUOUS_VIEW_TREE_CAPTURE = getDebugFlag(270395171,
             "CONTINUOUS_VIEW_TREE_CAPTURE", ENABLED, "Capture View tree every frame");
 
@@ -129,12 +142,14 @@
                     "Shrinks navbar when long pressing if ANIMATE_LPNH is enabled");
 
     public static final IntFlag LPNH_SLOP_PERCENTAGE =
-            getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
-                    "Controls touch slop percentage for lpnh");
+            FlagsFactory.getIntFlag(301680992, "LPNH_SLOP_PERCENTAGE", 100,
+                    "Controls touch slop percentage for lpnh",
+                    LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE);
 
     public static final IntFlag LPNH_TIMEOUT_MS =
-            getIntFlag(301680992, "LPNH_TIMEOUT_MS", ViewConfiguration.getLongPressTimeout(),
-                    "Controls lpnh timeout in milliseconds");
+            FlagsFactory.getIntFlag(301680992, "LPNH_TIMEOUT_MS",
+                    ViewConfiguration.getLongPressTimeout(),
+                    "Controls lpnh timeout in milliseconds", LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);
 
     public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
             270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
@@ -280,28 +295,35 @@
                     "Enables haptic hint at end of long pressing on the bottom bar nav handle.");
 
     public static final IntFlag LPNH_HAPTIC_HINT_START_SCALE_PERCENT =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
-            "Haptic hint start scale.");
+            FlagsFactory.getIntFlag(309972570,
+                    "LPNH_HAPTIC_HINT_START_SCALE_PERCENT", 0,
+                    "Haptic hint start scale.",
+                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT);
 
     public static final IntFlag LPNH_HAPTIC_HINT_END_SCALE_PERCENT =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
-            "Haptic hint end scale.");
+            FlagsFactory.getIntFlag(309972570,
+                    "LPNH_HAPTIC_HINT_END_SCALE_PERCENT", 100,
+                    "Haptic hint end scale.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT);
 
     public static final IntFlag LPNH_HAPTIC_HINT_SCALE_EXPONENT =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
-            "Haptic hint scale exponent.");
+            FlagsFactory.getIntFlag(309972570,
+                    "LPNH_HAPTIC_HINT_SCALE_EXPONENT", 1,
+                    "Haptic hint scale exponent.",
+                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
 
     public static final IntFlag LPNH_HAPTIC_HINT_ITERATIONS =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS", 50,
-            "Haptic hint number of iterations.");
+            FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_ITERATIONS",
+                    50,
+                    "Haptic hint number of iterations.",
+                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
 
     public static final BooleanFlag ENABLE_LPNH_DEEP_PRESS =
             getReleaseFlag(310952290, "ENABLE_LPNH_DEEP_PRESS", ENABLED,
                     "Long press of nav handle is instantly triggered if deep press is detected.");
 
     public static final IntFlag LPNH_HAPTIC_HINT_DELAY =
-            getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
-                    "Delay before haptic hint starts.");
+            FlagsFactory.getIntFlag(309972570, "LPNH_HAPTIC_HINT_DELAY", 0,
+                    "Delay before haptic hint starts.", LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
 
     // TODO(Block 17): Clean up flags
     // Aconfig migration complete for ENABLE_TASKBAR_PINNING.
@@ -314,12 +336,6 @@
         return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning();
     }
 
-    public static final BooleanFlag MOVE_STARTUP_DATA_TO_DEVICE_PROTECTED_STORAGE = getDebugFlag(
-            251502424, "ENABLE_BOOT_AWARE_STARTUP_DATA", DISABLED,
-            "Marks LauncherPref data as (and allows it to) available while the device is"
-                    + " locked. Enabling this causes a 1-time movement of certain SharedPreferences"
-                    + " data. Improves startup latency.");
-
     // Aconfig migration complete for ENABLE_APP_PAIRS.
     public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
             "ENABLE_APP_PAIRS", DISABLED,
@@ -413,10 +429,6 @@
             "ENABLE_ENFORCED_ROUNDED_CORNERS", ENABLED,
             "Enforce rounded corners on all App Widgets");
 
-    public static final BooleanFlag ENABLE_ICON_LABEL_AUTO_SCALING = getDebugFlag(270393294,
-            "ENABLE_ICON_LABEL_AUTO_SCALING", ENABLED,
-            "Enables scaling/spacing for icon labels to make more characters visible");
-
     public static final BooleanFlag USE_LOCAL_ICON_OVERRIDES = getDebugFlag(270394973,
             "USE_LOCAL_ICON_OVERRIDES", ENABLED,
             "Use inbuilt monochrome icons if app doesn't provide one");
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index f98cab6..44c41c1 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -26,7 +26,6 @@
 import android.text.TextUtils
 import android.util.Log
 import android.util.LongSparseArray
-import androidx.annotation.VisibleForTesting
 import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
@@ -89,7 +88,10 @@
         try {
             if (c.user == null) {
                 // User has been deleted, remove the item.
-                c.markDeleted("User has been deleted", RestoreError.PROFILE_DELETED)
+                c.markDeleted(
+                    "User has been deleted for item id=${c.id}",
+                    RestoreError.PROFILE_DELETED
+                )
                 return
             }
             when (c.itemType) {
@@ -127,29 +129,24 @@
      * data model to be bound to the launcher’s data model.
      */
     @SuppressLint("NewApi")
-    @VisibleForTesting
-    fun processAppOrDeepShortcut() {
+    private fun processAppOrDeepShortcut() {
         var allowMissingTarget = false
         var intent = c.parseIntent()
         if (intent == null) {
-            c.markDeleted("Invalid or null intent", RestoreError.MISSING_INFO)
+            c.markDeleted("Null intent for item id=${c.id}", RestoreError.MISSING_INFO)
             return
         }
         var disabledState =
             if (userManagerState.isUserQuiet(c.serialNumber))
                 WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER
             else 0
-        var cn = intent.component
-        val targetPkg = if (cn == null) intent.getPackage() else cn.packageName
-        if (TextUtils.isEmpty(targetPkg)) {
-            c.markDeleted("Shortcuts can't have null package", RestoreError.MISSING_INFO)
+        val cn = intent.component
+        val targetPkg = cn?.packageName ?: intent.getPackage()
+        if (targetPkg.isNullOrEmpty()) {
+            c.markDeleted("No target package for item id=${c.id}", RestoreError.MISSING_INFO)
             return
         }
-
-        // If there is no target package, it's an implicit intent
-        // (legacy shortcut) which is always valid
-        var validTarget =
-            (TextUtils.isEmpty(targetPkg) || launcherApps.isPackageEnabled(targetPkg, c.user))
+        var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user)
 
         // If it's a deep shortcut, we'll use pinned shortcuts to restore it
         if (cn != null && validTarget && (c.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
@@ -359,8 +356,7 @@
      * processing for folder content items is done in LoaderTask after all the items in the
      * workspace have been loaded. The loaded FolderInfos are stored in the BgDataModel.
      */
-    @VisibleForTesting
-    fun processFolderOrAppPair() {
+    private fun processFolderOrAppPair() {
         val folderInfo =
             bgDataModel.findOrMakeFolder(c.id).apply {
                 c.applyCommonProperties(this)
@@ -394,8 +390,7 @@
      * depending on the type of widget. Custom widgets are treated differently than non-custom
      * widgets, installing / restoring widgets are treated differently, etc.
      */
-    @VisibleForTesting
-    fun processWidget() {
+    private fun processWidget() {
         val component = ComponentName.unflattenFromString(c.appWidgetProvider)!!
         val appWidgetInfo = LauncherAppWidgetInfo(c.appWidgetId, component)
         c.applyCommonProperties(appWidgetInfo)
@@ -496,6 +491,7 @@
 
     companion object {
         private const val TAG = "WorkspaceItemProcessor"
+
         private fun logWidgetInfo(
             idp: InvariantDeviceProfile,
             widgetProviderInfo: LauncherAppWidgetProviderInfo
diff --git a/src/com/android/launcher3/util/VibratorWrapper.java b/src/com/android/launcher3/util/VibratorWrapper.java
index fef1c72..e1695e9 100644
--- a/src/com/android/launcher3/util/VibratorWrapper.java
+++ b/src/com/android/launcher3/util/VibratorWrapper.java
@@ -19,11 +19,11 @@
 import static android.os.VibrationEffect.createPredefined;
 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
 
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT;
-import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_DELAY;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_END_SCALE_PERCENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_ITERATIONS;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_SCALE_EXPONENT;
+import static com.android.launcher3.config.FeatureFlags.LPNH_HAPTIC_HINT_START_SCALE_PERCENT;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -39,7 +39,6 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 
@@ -257,17 +256,11 @@
     public void vibrateForSearchHint() {
         if (FeatureFlags.ENABLE_SEARCH_HAPTIC_HINT.get() && Utilities.ATLEAST_S
                 && mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
-            LauncherPrefs launcherPrefs = LauncherPrefs.get(mContext);
-            float startScale = launcherPrefs.get(
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_START_SCALE_PERCENT) / 100f;
-            float endScale = launcherPrefs.get(
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_END_SCALE_PERCENT) / 100f;
-            int scaleExponent = launcherPrefs.get(
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_SCALE_EXPONENT);
-            int iterations = launcherPrefs.get(
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_ITERATIONS);
-            int delayMs = launcherPrefs.get(
-                    LONG_PRESS_NAV_HANDLE_HAPTIC_HINT_DELAY);
+            float startScale = LPNH_HAPTIC_HINT_START_SCALE_PERCENT.get() / 100f;
+            float endScale = LPNH_HAPTIC_HINT_END_SCALE_PERCENT.get() / 100f;
+            int scaleExponent = LPNH_HAPTIC_HINT_SCALE_EXPONENT.get();
+            int iterations = LPNH_HAPTIC_HINT_ITERATIONS.get();
+            int delayMs = LPNH_HAPTIC_HINT_DELAY.get();
 
             VibrationEffect.Composition composition = VibrationEffect.startComposition();
             for (int i = 0; i < iterations; i++) {
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
index eb0494e..b193d37 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/flags/FlagsFactory.java
@@ -18,6 +18,9 @@
 
 import static com.android.launcher3.config.FeatureFlags.FlagState.ENABLED;
 
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.ConstantItem;
 import com.android.launcher3.config.FeatureFlags.BooleanFlag;
 import com.android.launcher3.config.FeatureFlags.FlagState;
 import com.android.launcher3.config.FeatureFlags.IntFlag;
@@ -55,6 +58,15 @@
     }
 
     /**
+     * Creates a new debug integer flag and it is saved in LauncherPrefs.
+     */
+    public static IntFlag getIntFlag(
+            int bugId, String key, int defaultValueInCode, String description,
+            @Nullable ConstantItem<Integer> launcherPrefFlag) {
+        return new IntFlag(defaultValueInCode);
+    }
+
+    /**
      * Dumps the current flags state to the print writer
      */
     public static void dump(PrintWriter pw) { }
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
index d8ae74b..9fb1989 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/CellLayoutTestUtils.java
@@ -41,7 +41,7 @@
 
                 CellPosMapper.CellPos pos = launcher.getCellPosMapper().mapPresenterToModel(
                         params.getCellX(), params.getCellY(),
-                        launcher.getWorkspace().getIdForScreen(cellLayout), CONTAINER_DESKTOP);
+                        launcher.getWorkspace().getCellLayoutId(cellLayout), CONTAINER_DESKTOP);
                 int screenId = pos.screenId;
                 for (int j = boards.size(); j <= screenId; j++) {
                     boards.add(new CellLayoutBoard(cellLayout.getCountX(), cellLayout.getCountY()));
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 79d8c60..f0d2e20 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -370,7 +370,8 @@
         Assert.assertTrue("Setup wizard is still visible", wizardDismissed);
     }
 
-    private static void verifyKeyguardInvisible() {
+    /** Asserts that keyguard is not visible */
+    public static void verifyKeyguardInvisible() {
         final boolean keyguardAlreadyVisible = sSeenKeyguard;
 
         sSeenKeyguard = sSeenKeyguard
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index d94e4a5..3a54915 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -44,6 +44,7 @@
             @Override
             public void evaluate() throws Throwable {
                 try {
+                    AbstractLauncherUiTest.verifyKeyguardInvisible();
                     mTest.mDevice.pressHome();
                     mTest.waitForLauncherCondition("Launcher activity wasn't created",
                             launcher -> launcher != null);
diff --git a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
index 0f0dd65..b63966d 100644
--- a/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
+++ b/tests/src/com/android/launcher3/celllayout/UnitTestCellLayoutBuilderRule.kt
@@ -21,6 +21,7 @@
 import android.view.View
 import androidx.test.core.app.ApplicationProvider
 import com.android.launcher3.CellLayout
+import com.android.launcher3.CellLayoutContainer
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.MultipageCellLayout
@@ -40,6 +41,17 @@
     private val applicationContext =
         ActivityContextWrapper(ApplicationProvider.getApplicationContext())
 
+    private val container =
+        object : CellLayoutContainer {
+            override fun getCellLayoutId(cellLayout: CellLayout): Int = 0
+
+            override fun getCellLayoutIndex(cellLayout: CellLayout): Int = 0
+
+            override fun getPanelCount(): Int = 1
+
+            override fun getPageDescription(pageIndex: Int): String = ""
+        }
+
     override fun starting(description: Description?) {
         val dp = getDeviceProfile()
         prevNumColumns = dp.inv.numColumns
@@ -60,7 +72,7 @@
         dp.cellLayoutBorderSpacePx = Point(0, 0)
         val cl =
             if (isMulti) MultipageCellLayout(getWrappedContext(applicationContext, dp))
-            else CellLayout(getWrappedContext(applicationContext, dp))
+            else CellLayout(getWrappedContext(applicationContext, dp), container)
         // I put a very large number for width and height so that all the items can fit, it doesn't
         // need to be exact, just bigger than the sum of cell border
         cl.measure(
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
new file mode 100644
index 0000000..e94dc02
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2024 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.appwidget.AppWidgetProviderInfo
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageInstaller
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import android.util.LongSparseArray
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.MISSING_INFO
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError.Companion.PROFILE_DELETED
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.widget.WidgetInflater
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.RETURNS_DEEP_STUBS
+import org.mockito.Mockito.mock
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class WorkspaceItemProcessorTest {
+    private var itemProcessor = createTestWorkspaceItemProcessor()
+
+    @Before
+    fun setup() {
+        itemProcessor = createTestWorkspaceItemProcessor()
+    }
+
+    @Test
+    fun `When user is null then mark item deleted`() {
+        // Given
+        val mockCursor = mock<LoaderCursor>().apply { id = 1 }
+        val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+        // When
+        itemProcessor.processItem()
+        // Then
+        verify(mockCursor).markDeleted("User has been deleted for item id=1", PROFILE_DELETED)
+    }
+
+    @Test
+    fun `When app has null intent then mark deleted`() {
+        // Given
+        val mockCursor =
+            mock<LoaderCursor>().apply {
+                user = UserHandle(0)
+                id = 1
+                itemType = ITEM_TYPE_APPLICATION
+            }
+        val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+        // When
+        itemProcessor.processItem()
+        // Then
+        verify(mockCursor).markDeleted("Null intent for item id=1", MISSING_INFO)
+    }
+
+    @Test
+    fun `When app has null target package then mark deleted`() {
+        // Given
+        val mockCursor =
+            mock<LoaderCursor>().apply {
+                user = UserHandle(0)
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                whenever(parseIntent()).thenReturn(Intent())
+            }
+        val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+        // When
+        itemProcessor.processItem()
+        // Then
+        verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+    }
+
+    @Test
+    fun `When app has empty String target package then mark deleted`() {
+        // Given
+        val mockIntent =
+            mock<Intent>().apply {
+                whenever(component).thenReturn(null)
+                whenever(`package`).thenReturn("")
+            }
+        val mockCursor =
+            mock<LoaderCursor>().apply {
+                user = UserHandle(0)
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                whenever(parseIntent()).thenReturn(mockIntent)
+            }
+        val itemProcessor = createTestWorkspaceItemProcessor(cursor = mockCursor)
+        // When
+        itemProcessor.processItem()
+        // Then
+        verify(mockCursor).markDeleted("No target package for item id=1", MISSING_INFO)
+    }
+
+    @Test
+    fun `When valid app then mark restored`() {
+        // Given
+        val userHandle = UserHandle(0)
+        val componentName = ComponentName("package", "class")
+        val mockIntent =
+            mock<Intent>().apply {
+                whenever(component).thenReturn(componentName)
+                whenever(`package`).thenReturn("")
+            }
+        val mockLauncherApps =
+            mock<LauncherApps>().apply {
+                whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+                whenever(isActivityEnabled(componentName, userHandle)).thenReturn(true)
+            }
+        val mockCursor =
+            mock<LoaderCursor>().apply {
+                user = userHandle
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                restoreFlag = 1
+                whenever(parseIntent()).thenReturn(mockIntent)
+                whenever(markRestored()).doAnswer { restoreFlag = 0 }
+            }
+        val itemProcessor =
+            createTestWorkspaceItemProcessor(cursor = mockCursor, launcherApps = mockLauncherApps)
+        // When
+        itemProcessor.processItem()
+        // Then
+        assertWithMessage("item restoreFlag should be set to 0")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(0)
+        // currently gets marked restored twice, although markRestore() has check for restoreFlag
+        verify(mockCursor, times(2)).markRestored()
+    }
+
+    @Test
+    fun `When fallback Activity found for app then mark restored`() {
+        // Given
+        val userHandle = UserHandle(0)
+        val componentName = ComponentName("package", "class")
+        val mockIntent =
+            mock<Intent>().apply {
+                whenever(component).thenReturn(componentName)
+                whenever(`package`).thenReturn("")
+                whenever(toUri(0)).thenReturn("")
+            }
+        val mockLauncherApps =
+            mock<LauncherApps>().apply {
+                whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+                whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+            }
+        val mockPmHelper =
+            mock<PackageManagerHelper>().apply {
+                whenever(getAppLaunchIntent(componentName.packageName, userHandle))
+                    .thenReturn(mockIntent)
+            }
+        val mockCursor =
+            mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
+                user = userHandle
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                restoreFlag = 1
+                whenever(parseIntent()).thenReturn(mockIntent)
+                whenever(markRestored()).doAnswer { restoreFlag = 0 }
+                whenever(updater().put(Favorites.INTENT, mockIntent.toUri(0)).commit())
+                    .thenReturn(1)
+            }
+        val itemProcessor =
+            createTestWorkspaceItemProcessor(
+                cursor = mockCursor,
+                launcherApps = mockLauncherApps,
+                pmHelper = mockPmHelper
+            )
+        // When
+        itemProcessor.processItem()
+        // Then
+        assertWithMessage("item restoreFlag should be set to 0")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(0)
+        verify(mockCursor.updater().put(Favorites.INTENT, mockIntent.toUri(0))).commit()
+    }
+
+    @Test
+    fun `When app with disabled activity and no fallback found then mark deleted`() {
+        // Given
+        val userHandle = UserHandle(0)
+        val componentName = ComponentName("package", "class")
+        val mockIntent =
+            mock<Intent>().apply {
+                whenever(component).thenReturn(componentName)
+                whenever(`package`).thenReturn("")
+            }
+        val mockLauncherApps =
+            mock<LauncherApps>().apply {
+                whenever(isPackageEnabled("package", userHandle)).thenReturn(true)
+                whenever(isActivityEnabled(componentName, userHandle)).thenReturn(false)
+            }
+        val mockPmHelper =
+            mock<PackageManagerHelper>().apply {
+                whenever(getAppLaunchIntent(componentName.packageName, userHandle)).thenReturn(null)
+            }
+        val mockCursor =
+            mock<LoaderCursor>().apply {
+                user = userHandle
+                itemType = ITEM_TYPE_APPLICATION
+                id = 1
+                restoreFlag = 1
+                whenever(parseIntent()).thenReturn(mockIntent)
+            }
+        val itemProcessor =
+            createTestWorkspaceItemProcessor(
+                cursor = mockCursor,
+                launcherApps = mockLauncherApps,
+                pmHelper = mockPmHelper
+            )
+        // When
+        itemProcessor.processItem()
+        // Then
+        assertWithMessage("item restoreFlag should be unchanged")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(1)
+        verify(mockCursor).markDeleted("Intent null, unable to find a launch target", MISSING_INFO)
+    }
+
+    /**
+     * Helper to create WorkspaceItemProcessor with defaults. WorkspaceItemProcessor has a lot of
+     * dependencies, so this method can be used to inject concrete arguments while keeping the rest
+     * as mocks/defaults.
+     */
+    private fun createTestWorkspaceItemProcessor(
+        cursor: LoaderCursor = mock(),
+        memoryLogger: LoaderMemoryLogger? = null,
+        userManagerState: UserManagerState = mock(),
+        launcherApps: LauncherApps = mock(),
+        shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mapOf(),
+        app: LauncherAppState = mock(),
+        bgDataModel: BgDataModel = mock(),
+        widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?> = mutableMapOf(),
+        widgetInflater: WidgetInflater = mock(),
+        pmHelper: PackageManagerHelper = mock(),
+        iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf(),
+        isSdCardReady: Boolean = false,
+        pendingPackages: MutableSet<PackageUserKey> = mutableSetOf(),
+        unlockedUsers: LongSparseArray<Boolean> = LongSparseArray(),
+        installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo> = hashMapOf(),
+        allDeepShortcuts: MutableList<ShortcutInfo> = mutableListOf()
+    ) =
+        WorkspaceItemProcessor(
+            c = cursor,
+            memoryLogger = memoryLogger,
+            userManagerState = userManagerState,
+            launcherApps = launcherApps,
+            app = app,
+            bgDataModel = bgDataModel,
+            widgetProvidersMap = widgetProvidersMap,
+            widgetInflater = widgetInflater,
+            pmHelper = pmHelper,
+            unlockedUsers = unlockedUsers,
+            iconRequestInfos = iconRequestInfos,
+            pendingPackages = pendingPackages,
+            isSdCardReady = isSdCardReady,
+            shortcutKeyToPinnedShortcuts = shortcutKeyToPinnedShortcuts,
+            installingPkgs = installingPkgs,
+            allDeepShortcuts = allDeepShortcuts
+        )
+}
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index db38c68..ec226af 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.ui.widget;
 
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
+
 import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.PlatinumTest;
@@ -30,10 +33,11 @@
 import com.android.launcher3.ui.TestViewHelpers;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
+import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
+import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -89,10 +93,13 @@
      * A custom shortcut is a 1x1 widget that launches a specific intent when user tap on it.
      * Custom shortcuts are replaced by deep shortcuts after api 25.
      */
-    @Ignore
+    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     @Test
     @PortraitLandscape
     public void testDragCustomShortcut() throws Throwable {
+        // TODO(b/322820039): Enable test for tablets - the picker UI has changed and test needs to
+        //  be updated to look for appropriate UI elements.
+        Assume.assumeFalse(mLauncher.isTablet());
         new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
 
         mLauncher.getWorkspace().openAllWidgets()
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 3693163..43fc8ff 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -349,7 +349,7 @@
         assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
         for (int i = 0; i < pageCount; i++) {
             CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
-            int pageId = launcher.getWorkspace().getIdForScreen(page);
+            int pageId = launcher.getWorkspace().getCellLayoutId(page);
             assertEquals("The page's id at index " + i + " does NOT match.", pageId,
                     pageIds[i]);
         }