Merge "Import translations. DO NOT MERGE ANYWHERE" into tm-qpr-dev
diff --git a/quickstep/res/values-land/dimens.xml b/quickstep/res/values-land/dimens.xml
index bc5d02a..30983c4 100644
--- a/quickstep/res/values-land/dimens.xml
+++ b/quickstep/res/values-land/dimens.xml
@@ -76,10 +76,8 @@
     <dimen name="gesture_tutorial_taskbar_padding_start_end">218dp</dimen>
 
     <!--  Taskbar 3 button spacing  -->
-    <dimen name="taskbar_button_margin_5_5">94.5dp</dimen>
+    <dimen name="taskbar_button_margin_split">88dp</dimen>
     <dimen name="taskbar_button_margin_6_5">219.6dp</dimen>
-    <dimen name="taskbar_button_margin_4_5">84dp</dimen>
-    <dimen name="taskbar_button_margin_4_4">79dp</dimen>
     <dimen name="taskbar_contextual_button_margin">48dp</dimen>
     <dimen name="taskbar_suw_frame">96dp</dimen>
     <dimen name="taskbar_suw_insets">24dp</dimen>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 198a676..a91507c 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -25,6 +25,7 @@
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
     <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
     <string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
+    <string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
 
     <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
          determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index baf097e..7a3c35f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -303,11 +303,9 @@
     <!--  Taskbar 3 button spacing  -->
     <dimen name="taskbar_button_space_inbetween">24dp</dimen>
     <dimen name="taskbar_button_space_inbetween_phone">40dp</dimen>
-    <dimen name="taskbar_button_margin_5_5">26dp</dimen>
+    <dimen name="taskbar_button_margin_split">48dp</dimen>
     <dimen name="taskbar_button_margin_6_5">75dp</dimen>
-    <dimen name="taskbar_button_margin_4_5">47dp</dimen>
-    <dimen name="taskbar_button_margin_4_4">47dp</dimen>
-    <dimen name="taskbar_button_margin_default">47dp</dimen>
+    <dimen name="taskbar_button_margin_default">48dp</dimen>
 
     <!-- Launcher splash screen -->
     <!-- Note: keep this value in sync with the WindowManager/Shell dimens.xml -->
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 902cf29..6d74526 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -152,10 +152,10 @@
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
             unfoldTransitionProgressProvider) {
         super(windowContext);
-        mDeviceProfile = launcherDp.copy(this);
-
         final Resources resources = getResources();
 
+        matchDeviceProfile(launcherDp, getResources());
+
         mNavMode = DisplayController.getNavigationMode(windowContext);
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
         mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
@@ -170,8 +170,6 @@
         mIsNavBarKidsMode = settingsCache.getValue(
                 Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
 
-        updateIconSize(resources);
-
         // Get display and corners first, as views might use them in constructor.
         Display display = windowContext.getDisplay();
         Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
@@ -259,8 +257,7 @@
     public void updateDeviceProfile(DeviceProfile launcherDp, NavigationMode navMode) {
         mNavMode = navMode;
         mControllers.taskbarOverlayController.updateLauncherDeviceProfile(launcherDp);
-        mDeviceProfile = launcherDp.copy(this);
-        updateIconSize(getResources());
+        matchDeviceProfile(launcherDp, getResources());
 
         AbstractFloatingView.closeAllOpenViewsExcept(this, false, TYPE_REBIND_SAFE);
         // Reapply fullscreen to take potential new screen size into account.
@@ -269,6 +266,21 @@
         dispatchDeviceProfileChanged();
     }
 
+    /**
+     * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
+     * the icon size
+     */
+    private void matchDeviceProfile(DeviceProfile originDeviceProfile, Resources resources) {
+        mDeviceProfile = originDeviceProfile.copy(this);
+        // Taskbar should match the number of icons of hotseat
+        mDeviceProfile.numShownHotseatIcons = originDeviceProfile.numShownHotseatIcons;
+        // Same QSB width to have a smooth animation
+        mDeviceProfile.hotseatQsbWidth = originDeviceProfile.hotseatQsbWidth;
+        // Update the size of the icons
+        updateIconSize(resources);
+    }
+
+
     private void updateIconSize(Resources resources) {
         mDeviceProfile.iconSizePx = resources.getDimensionPixelSize(
                 DisplayController.isTransientTaskbar(this)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
index 379a6cd..ca7ce74 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,14 +17,9 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
-import android.appwidget.AppWidgetHost;
 import android.content.pm.ShortcutInfo;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.Utilities;
-import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /**
  * A wrapper for the hidden API calls
@@ -37,14 +32,4 @@
         Person[] persons = si.getPersons();
         return persons == null ? Utilities.EMPTY_PERSON_ARRAY : persons;
     }
-
-    /**
-     * Set the interaction handler for the host
-     * @param host AppWidgetHost that needs the interaction handler
-     * @param handler InteractionHandler for the views in the host
-     */
-    public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
-            @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
-        host.setInteractionHandler(handler::onInteraction);
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
new file mode 100644
index 0000000..6659fa0
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepAppWidgetHost.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.os.Looper;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+
+import java.util.function.IntConsumer;
+
+/**
+ * {@link AppWidgetHost} that is used to receive the changes to the widgets without
+ * storing any {@code Activity} info like that of the launcher.
+ */
+final class QuickstepAppWidgetHost extends AppWidgetHost {
+    private final @NonNull Context mContext;
+    private final @NonNull IntConsumer mAppWidgetRemovedCallback;
+    private final @NonNull LauncherWidgetHolder.ProviderChangedListener mProvidersChangedListener;
+
+    QuickstepAppWidgetHost(@NonNull Context context, @NonNull IntConsumer appWidgetRemovedCallback,
+            @NonNull LauncherWidgetHolder.ProviderChangedListener listener,
+            @NonNull Looper looper) {
+        super(context, APPWIDGET_HOST_ID, null, looper);
+        mContext = context;
+        mAppWidgetRemovedCallback = appWidgetRemovedCallback;
+        mProvidersChangedListener = listener;
+    }
+
+    @Override
+    protected void onProvidersChanged() {
+        mProvidersChangedListener.notifyWidgetProvidersChanged();
+    }
+
+    @Override
+    public void onAppWidgetRemoved(int appWidgetId) {
+        mAppWidgetRemovedCallback.accept(appWidgetId);
+    }
+
+    @Override
+    protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
+        LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
+                mContext, appWidget);
+        super.onProviderChanged(appWidgetId, info);
+        // The super method updates the dimensions of the providerInfo. Update the
+        // launcher spans accordingly.
+        info.initSpans(mContext, LauncherAppState.getIDP(mContext));
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
index 353d817..08d147f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepInteractionHandler.java
@@ -34,11 +34,9 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /** Provides a Quickstep specific animation when launching an activity from an app widget. */
-class QuickstepInteractionHandler
-        implements LauncherWidgetHolder.LauncherWidgetInteractionHandler {
+class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
 
     private static final String TAG = "QuickstepInteractionHandler";
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 13d0be5..a07e4d7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -96,7 +96,6 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.proxy.ProxyActivityStarter;
 import com.android.launcher3.proxy.StartActivityParams;
@@ -106,6 +105,7 @@
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
 import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
 import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
 import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
@@ -517,9 +517,11 @@
 
     @Override
     protected LauncherWidgetHolder createAppWidgetHolder() {
-        LauncherWidgetHolder appWidgetHolder = super.createAppWidgetHolder();
-        appWidgetHolder.setInteractionHandler(new QuickstepInteractionHandler(this));
-        return appWidgetHolder;
+        final QuickstepHolderFactory factory =
+                (QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
+        return factory.newInstance(this,
+                appWidgetId -> getWorkspace().removeWidget(appWidgetId),
+                new QuickstepInteractionHandler(this));
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
new file mode 100644
index 0000000..a8edd51
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -0,0 +1,270 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.WidgetsModel;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+
+/**
+ * {@link LauncherWidgetHolder} that puts the app widget host in the background
+ */
+public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
+    private static final List<QuickstepWidgetHolder> sHolders = new ArrayList<>();
+    private static final SparseArray<QuickstepWidgetHolderListener> sListeners =
+            new SparseArray<>();
+
+    private static AppWidgetHost sWidgetHost = null;
+
+    private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
+
+    private final @NonNull IntConsumer mAppWidgetRemovedCallback;
+
+    private final ArrayList<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
+
+    @Thunk
+    QuickstepWidgetHolder(@NonNull Context context,
+            @Nullable IntConsumer appWidgetRemovedCallback,
+            @Nullable RemoteViews.InteractionHandler interactionHandler) {
+        super(context, appWidgetRemovedCallback);
+        mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
+                : i -> {};
+        mInteractionHandler = interactionHandler;
+        sHolders.add(this);
+    }
+
+    @Override
+    @NonNull
+    protected AppWidgetHost createHost(@NonNull Context context,
+            @Nullable IntConsumer appWidgetRemovedCallback) {
+        if (sWidgetHost == null) {
+            sWidgetHost = new QuickstepAppWidgetHost(context.getApplicationContext(),
+                    i -> MAIN_EXECUTOR.execute(() ->
+                            sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
+                    () -> MAIN_EXECUTOR.execute(() ->
+                            sHolders.forEach(h -> h.mProviderChangedListeners.forEach(
+                            ProviderChangedListener::notifyWidgetProvidersChanged))),
+                    UI_HELPER_EXECUTOR.getLooper());
+            if (!WidgetsModel.GO_DISABLE_WIDGETS) {
+                sWidgetHost.startListening();
+            }
+        }
+        return sWidgetHost;
+    }
+
+    /**
+     * Delete the specified app widget from the host
+     * @param appWidgetId The ID of the app widget to be deleted
+     */
+    @Override
+    public void deleteAppWidgetId(int appWidgetId) {
+        super.deleteAppWidgetId(appWidgetId);
+        sListeners.remove(appWidgetId);
+    }
+
+    /**
+     * Called when the launcher is destroyed
+     */
+    @Override
+    public void destroy() {
+        sHolders.remove(this);
+    }
+
+    /**
+     * Add a listener that is triggered when the providers of the widgets are changed
+     * @param listener The listener that notifies when the providers changed
+     */
+    @Override
+    public void addProviderChangeListener(
+            @NonNull LauncherWidgetHolder.ProviderChangedListener listener) {
+        mProviderChangedListeners.add(listener);
+    }
+
+    /**
+     * Remove the specified listener from the host
+     * @param listener The listener that is to be removed from the host
+     */
+    @Override
+    public void removeProviderChangeListener(
+            LauncherWidgetHolder.ProviderChangedListener listener) {
+        mProviderChangedListeners.remove(listener);
+    }
+
+    /**
+     * Stop the host from updating the widget views
+     */
+    @Override
+    public void stopListening() {
+        if (WidgetsModel.GO_DISABLE_WIDGETS) {
+            return;
+        }
+
+        sWidgetHost.setAppWidgetHidden();
+        setListeningFlag(false);
+    }
+
+    /**
+     * Create a view for the specified app widget
+     * @param context The activity context for which the view is created
+     * @param appWidgetId The ID of the widget
+     * @param appWidget The {@link LauncherAppWidgetProviderInfo} of the widget
+     * @return A view for the widget
+     */
+    @NonNull
+    @Override
+    public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
+            @NonNull LauncherAppWidgetProviderInfo appWidget) {
+        LauncherAppWidgetHostView widgetView = getPendingView(appWidgetId);
+        if (widgetView != null) {
+            removePendingView(appWidgetId);
+        } else {
+            widgetView = new LauncherAppWidgetHostView(context);
+        }
+        widgetView.setInteractionHandler(mInteractionHandler);
+        widgetView.setAppWidget(appWidgetId, appWidget);
+
+        QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
+        if (listener == null) {
+            listener = new QuickstepWidgetHolderListener(this, widgetView);
+            sWidgetHost.setListener(appWidgetId, listener);
+            sListeners.put(appWidgetId, listener);
+        } else {
+            listener.resetView(this, widgetView);
+        }
+
+        return widgetView;
+    }
+
+    /**
+     * Clears all the views from the host
+     */
+    @Override
+    public void clearViews() {
+        for (int i = sListeners.size() - 1; i >= 0; i--) {
+            sListeners.valueAt(i).mView.remove(this);
+        }
+    }
+
+    private static class QuickstepWidgetHolderListener
+            implements AppWidgetHost.AppWidgetHostListener {
+        @NonNull
+        private final Map<QuickstepWidgetHolder, AppWidgetHostView> mView = new WeakHashMap<>();
+
+        @Nullable
+        private RemoteViews mRemoteViews = null;
+
+        QuickstepWidgetHolderListener(@NonNull QuickstepWidgetHolder holder,
+                @NonNull LauncherAppWidgetHostView view) {
+            mView.put(holder, view);
+        }
+
+        @UiThread
+        public void resetView(@NonNull QuickstepWidgetHolder holder,
+                @NonNull AppWidgetHostView view) {
+            mView.put(holder, view);
+            view.updateAppWidget(mRemoteViews);
+        }
+
+        @Override
+        @WorkerThread
+        public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
+            mRemoteViews = null;
+            executeOnMainExecutor(v -> v.onUpdateProviderInfo(info));
+        }
+
+        @Override
+        @WorkerThread
+        public void updateAppWidget(@Nullable RemoteViews views) {
+            mRemoteViews = views;
+            executeOnMainExecutor(v -> v.updateAppWidget(mRemoteViews));
+        }
+
+        @Override
+        @WorkerThread
+        public void onViewDataChanged(int viewId) {
+            executeOnMainExecutor(v -> v.onViewDataChanged(viewId));
+        }
+
+        private void executeOnMainExecutor(Consumer<AppWidgetHostView> consumer) {
+            MAIN_EXECUTOR.execute(() -> mView.values().forEach(consumer));
+        }
+    }
+
+    /**
+     * {@code HolderFactory} subclass that takes an interaction handler as one of the parameters
+     * when creating a new instance.
+     */
+    public static class QuickstepHolderFactory extends HolderFactory {
+
+        @SuppressWarnings("unused")
+        public QuickstepHolderFactory(Context context) { }
+
+        @Override
+        public LauncherWidgetHolder newInstance(@NonNull Context context,
+                @Nullable IntConsumer appWidgetRemovedCallback) {
+            return newInstance(context, appWidgetRemovedCallback, null);
+        }
+
+        /**
+         * @param context The context of the caller
+         * @param appWidgetRemovedCallback The callback that is called when widgets are removed
+         * @param interactionHandler The interaction handler when the widgets are clicked
+         * @return A new {@link LauncherWidgetHolder} instance
+         */
+        public LauncherWidgetHolder newInstance(@NonNull Context context,
+                @Nullable IntConsumer appWidgetRemovedCallback,
+                @Nullable RemoteViews.InteractionHandler interactionHandler) {
+
+            if (!FeatureFlags.ENABLE_WIDGET_HOST_IN_BACKGROUND.get()) {
+                return new LauncherWidgetHolder(context, appWidgetRemovedCallback) {
+                    @Override
+                    protected AppWidgetHost createHost(Context context,
+                            @Nullable IntConsumer appWidgetRemovedCallback) {
+                        AppWidgetHost host = super.createHost(context, appWidgetRemovedCallback);
+                        host.setInteractionHandler(interactionHandler);
+                        return host;
+                    }
+                };
+            }
+            return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 877e28a..cecf58d 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -108,7 +108,10 @@
         float depth = mDepth;
         IBinder windowToken = mLauncher.getRootView().getWindowToken();
         if (windowToken != null) {
-            mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+            // The API's full zoom-out is three times larger than the zoom-out we apply to the
+            // icons. To keep the two consistent throughout the animation while keeping Launcher's
+            // concept of full depth unchanged, we divide the depth by 3 here.
+            mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
         }
 
         if (!BlurUtils.supportsBlursOnWindows()) {
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
index 0303bc1..4837c6c 100644
--- a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
@@ -39,12 +39,12 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(558)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
         assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        assertThat(dp.hotseatBorderSpace).isEqualTo(69)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(70)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(176)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(580)
 
         assertThat(dp.isQsbInline).isFalse()
         assertThat(dp.hotseatQsbWidth).isEqualTo(1445)
@@ -61,12 +61,12 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(558)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(510)
         assertThat(dp.numShownHotseatIcons).isEqualTo(4)
-        assertThat(dp.hotseatBorderSpace).isEqualTo(50)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(40)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(112)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(558)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(150)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(550)
 
         assertThat(dp.isQsbInline).isFalse()
         assertThat(dp.hotseatQsbWidth).isEqualTo(1080)
@@ -82,12 +82,12 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(744)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
         assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        assertThat(dp.hotseatBorderSpace).isEqualTo(82)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(54)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(106)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(744)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(231)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(759)
 
         assertThat(dp.isQsbInline).isFalse()
         assertThat(dp.hotseatQsbWidth).isEqualTo(1468)
@@ -107,12 +107,12 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
         assertThat(dp.numShownHotseatIcons).isEqualTo(6)
-        assertThat(dp.hotseatBorderSpace).isEqualTo(102)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(100)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(625)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(300)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(1040)
 
         assertThat(dp.isQsbInline).isFalse()
         assertThat(dp.hotseatQsbWidth).isEqualTo(1233)
@@ -128,15 +128,15 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
         assertThat(dp.numShownHotseatIcons).isEqualTo(6)
         assertThat(dp.hotseatBorderSpace).isEqualTo(36)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(854)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(864)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(696)
 
         assertThat(dp.isQsbInline).isTrue()
-        assertThat(dp.hotseatQsbWidth).isEqualTo(531)
+        assertThat(dp.hotseatQsbWidth).isEqualTo(528)
     }
 
     /**
@@ -150,12 +150,12 @@
         val dp = newDP()
         dp.isTaskbarPresentInApps = true
 
-        assertThat(dp.hotseatBarEndOffset).isEqualTo(705)
+        assertThat(dp.hotseatBarEndOffset).isEqualTo(660)
         assertThat(dp.numShownHotseatIcons).isEqualTo(5)
-        assertThat(dp.hotseatBorderSpace).isEqualTo(43)
+        assertThat(dp.hotseatBorderSpace).isEqualTo(36)
 
-        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(782)
-        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(705)
+        assertThat(dp.getHotseatLayoutPadding(context).left).isEqualTo(816)
+        assertThat(dp.getHotseatLayoutPadding(context).right).isEqualTo(700)
 
         assertThat(dp.isQsbInline).isTrue()
         assertThat(dp.hotseatQsbWidth).isEqualTo(480)
diff --git a/res/values/config.xml b/res/values/config.xml
index 4cab2de..016420b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -83,6 +83,7 @@
     <string name="model_delegate_class" translatable="false"></string>
     <string name="window_manager_proxy_class" translatable="false"></string>
     <string name="secondary_display_predictions_class" translatable="false"></string>
+    <string name="widget_holder_factory_class" translatable="false"></string>
 
     <!-- View ID to use for QSB widget -->
     <item type="id" name="qsb_widget" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index dc53552..664c2f3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -114,6 +114,8 @@
     <dimen name="all_apps_tabs_indicator_height">2dp</dimen>
     <dimen name="all_apps_header_top_margin">33dp</dimen>
     <dimen name="all_apps_header_top_padding">36dp</dimen>
+    <!-- Additional top padding to add when Floating Searchbar is enabled. -->
+    <dimen name="all_apps_additional_top_padding_floating_search">16dp</dimen>
     <dimen name="all_apps_header_bottom_padding">14dp</dimen>
     <dimen name="all_apps_header_top_adjustment">6dp</dimen>
     <dimen name="all_apps_header_bottom_adjustment">4dp</dimen>
@@ -359,6 +361,7 @@
     <dimen name="qsb_widget_height">0dp</dimen>
     <dimen name="qsb_shadow_height">0dp</dimen>
     <dimen name="min_hotseat_icon_space">18dp</dimen>
+    <dimen name="max_hotseat_icon_space">50dp</dimen>
     <dimen name="min_hotseat_qsb_width">0dp</dimen>
     <dimen name="taskbar_icon_size">44dp</dimen>
     <dimen name="transient_taskbar_icon_size">57dp</dimen>
@@ -381,10 +384,8 @@
     <dimen name="taskbar_button_margin_default">0dp</dimen>
     <dimen name="taskbar_button_space_inbetween">0dp</dimen>
     <dimen name="taskbar_button_space_inbetween_phone">0dp</dimen>
-    <dimen name="taskbar_button_margin_5_5">0dp</dimen>
+    <dimen name="taskbar_button_margin_split">0dp</dimen>
     <dimen name="taskbar_button_margin_6_5">0dp</dimen>
-    <dimen name="taskbar_button_margin_4_5">0dp</dimen>
-    <dimen name="taskbar_button_margin_4_4">0dp</dimen>
     <!-- Taskbar swipe up thresholds threshold -->
     <dimen name="taskbar_nav_threshold">0dp</dimen>
     <dimen name="taskbar_app_window_threshold">0dp</dimen>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 407f217..c9a44a1 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -57,8 +57,9 @@
         launcher:numFolderRows="3"
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="4"
+        launcher:numExtendedHotseatIcons="6"
         launcher:dbFile="launcher_4_by_4.db"
-        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_4_4"
+        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
         launcher:defaultLayoutId="@xml/default_workspace_4x4"
         launcher:deviceCategory="phone|multi_display" >
 
@@ -121,8 +122,9 @@
         launcher:numFolderRows="4"
         launcher:numFolderColumns="4"
         launcher:numHotseatIcons="5"
+        launcher:numExtendedHotseatIcons="6"
         launcher:dbFile="launcher.db"
-        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_5_5"
+        launcher:inlineNavButtonsEndSpacing="@dimen/taskbar_button_margin_split"
         launcher:defaultLayoutId="@xml/default_workspace_5x5"
         launcher:deviceCategory="phone|multi_display" >
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 7ccd195..b95b0af 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -188,6 +188,10 @@
     public final int hotseatQsbVisualHeight;
     private final int hotseatQsbShadowHeight;
     public int hotseatBorderSpace;
+    private int minHotseatIconSpacePx;
+    private int minHotseatQsbWidthPx;
+    private final int maxHotseatIconSpacePx;
+    private int inlineNavButtonsEndSpacing;
 
     // Bottom sheets
     public int bottomSheetTopPadding;
@@ -434,7 +438,7 @@
                 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
                 : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
                 && hotseatQsbHeight > 0;
-        isQsbInline = inv.inlineQsb[mTypeIndex] && canQsbInline;
+        isQsbInline = isScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
 
         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
         numShownHotseatIcons =
@@ -474,17 +478,17 @@
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
         updateHotseatSizes(pxFromDp(inv.iconSize[INDEX_DEFAULT], mMetrics));
         if (areNavButtonsInline && !isPhone) {
+            inlineNavButtonsEndSpacing = res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing);
             /*
              * 3 nav buttons +
              * Spacing between nav buttons +
-             * Little space at the end for contextual buttons +
-             * Little space between icons and nav buttons
+             * Space at the end for contextual buttons
              */
             hotseatBarEndOffset = 3 * res.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
                     + 2 * res.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween)
-                    + res.getDimensionPixelSize(inv.inlineNavButtonsEndSpacing)
-                    + res.getDimensionPixelSize(R.dimen.taskbar_hotseat_nav_spacing);
+                    + inlineNavButtonsEndSpacing;
         } else {
+            inlineNavButtonsEndSpacing = 0;
             hotseatBarEndOffset = 0;
         }
 
@@ -531,8 +535,12 @@
                 cellLayoutPadding);
         updateWorkspacePadding();
 
+        minHotseatIconSpacePx = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
+        minHotseatQsbWidthPx = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
+        maxHotseatIconSpacePx = areNavButtonsInline
+                ? res.getDimensionPixelSize(R.dimen.max_hotseat_icon_space) : Integer.MAX_VALUE;
         // Hotseat and QSB width depends on updated cellSize and workspace padding
-        recalculateHotseatWidthAndBorderSpace(res);
+        recalculateHotseatWidthAndBorderSpace();
 
         // AllApps height calculation depends on updated cellSize
         if (isTablet) {
@@ -621,68 +629,57 @@
         }
     }
 
-    private void recalculateHotseatWidthAndBorderSpace(Resources res) {
-        hotseatBorderSpace = calculateHotseatBorderSpace();
+    /**
+     * Calculates the width of the hotseat, changing spaces between the icons and removing icons if
+     * necessary.
+     */
+    public void recalculateHotseatWidthAndBorderSpace() {
+        if (!isScalableGrid) return;
+
+        int columns = inv.hotseatColumnSpan[mTypeIndex];
+        float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+        hotseatBorderSpace = calculateHotseatBorderSpace(hotseatWidthPx, /* numExtraBorder= */ 0);
         hotseatQsbWidth = calculateQsbWidth(hotseatBorderSpace);
-        // Spaces should be correct when there nav buttons are not inline
+        // Spaces should be correct when the nav buttons are not inline
         if (!areNavButtonsInline) {
             return;
         }
 
-        // Get the maximum width that the hotseat can be
-        int columns = getPanelCount() * inv.numColumns;
-        int maxHotseatWidth = getIconToIconWidthForColumns(columns);
-        int sideSpace = (availableWidthPx - maxHotseatWidth) / 2;
-        int inlineButtonsOverlap = Math.max(0, hotseatBarEndOffset - sideSpace);
-        // decrease how much the nav buttons go "inside" the hotseat
-        maxHotseatWidth -= inlineButtonsOverlap;
+        // The side space with inline buttons should be what is defined in InvariantDeviceProfile
+        int sideSpace = inlineNavButtonsEndSpacing;
+        int maxHotseatWidth = availableWidthPx - sideSpace - hotseatBarEndOffset;
+        int maxHotseatIconsWidth = maxHotseatWidth - (isQsbInline ? hotseatQsbWidth : 0);
+        hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidth,
+                (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
 
-        // Get how much space is required to show the hotseat with QSB
-        int requiredWidth = getHotseatRequiredWidth();
-
-        // If spaces are fine, use them
-        if (requiredWidth <= maxHotseatWidth) {
-            return;
-        }
-
-        // Calculate the difference of widths and remove a little from each space between icons
-        // and QSB if it's inline
-        int spaceDiff = requiredWidth - maxHotseatWidth;
-        int numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
-        hotseatBorderSpace -= (spaceDiff / numOfSpaces);
-
-        int minHotseatIconSpaceDp = res.getDimensionPixelSize(R.dimen.min_hotseat_icon_space);
-        int minHotseatQsbWidthDp = res.getDimensionPixelSize(R.dimen.min_hotseat_qsb_width);
-
-        if (hotseatBorderSpace >= minHotseatIconSpaceDp) {
+        if (hotseatBorderSpace >= minHotseatIconSpacePx) {
             return;
         }
 
         // Border space can't be less than the minimum
-        hotseatBorderSpace = minHotseatIconSpaceDp;
-        requiredWidth = getHotseatRequiredWidth();
+        hotseatBorderSpace = minHotseatIconSpacePx;
+        int requiredWidth = getHotseatRequiredWidth();
 
         // If there is an inline qsb, change its size
         if (isQsbInline) {
             hotseatQsbWidth -= requiredWidth - maxHotseatWidth;
-            if (hotseatQsbWidth >= minHotseatQsbWidthDp) {
+            if (hotseatQsbWidth >= minHotseatQsbWidthPx) {
                 return;
             }
 
             // QSB can't be less than the minimum
-            hotseatQsbWidth = minHotseatQsbWidthDp;
+            hotseatQsbWidth = minHotseatQsbWidthPx;
         }
 
+        maxHotseatIconsWidth = maxHotseatWidth - (isQsbInline ? hotseatQsbWidth : 0);
+
         // If it still doesn't fit, start removing icons
         do {
             numShownHotseatIcons--;
-            requiredWidth = getHotseatRequiredWidth();
-        } while (requiredWidth > maxHotseatWidth && numShownHotseatIcons > 1);
+            hotseatBorderSpace = calculateHotseatBorderSpace(maxHotseatIconsWidth,
+                    (isQsbInline ? 1 : 0) + /* border between nav buttons and first icon */ 1);
+        } while (hotseatBorderSpace < minHotseatIconSpacePx && numShownHotseatIcons > 1);
 
-        // Add back some space between the icons
-        spaceDiff = maxHotseatWidth - requiredWidth;
-        numOfSpaces = numShownHotseatIcons - (isQsbInline ? 0 : 1);
-        hotseatBorderSpace += (spaceDiff / numOfSpaces);
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp) {
@@ -954,15 +951,14 @@
     }
 
     /**
-     * Hotseat width spans a certain number of columns on scalable grids.
-     * This method calculates the space between the icons to achieve that width.
+     * This method calculates the space between the icons to achieve a certain width.
      */
-    private int calculateHotseatBorderSpace() {
-        if (!isScalableGrid) return 0;
-        int columns = inv.hotseatColumnSpan[mTypeIndex];
-        float hotseatWidthPx = getIconToIconWidthForColumns(columns);
+    private int calculateHotseatBorderSpace(float hotseatWidthPx, int numExtraBorder) {
         float hotseatIconsTotalPx = iconSizePx * numShownHotseatIcons;
-        return (int) (hotseatWidthPx - hotseatIconsTotalPx) / (numShownHotseatIcons - 1);
+        int hotseatBorderSpace =
+                (int) (hotseatWidthPx - hotseatIconsTotalPx)
+                        / (numShownHotseatIcons - 1 + numExtraBorder);
+        return Math.min(hotseatBorderSpace, maxHotseatIconSpacePx);
     }
 
 
@@ -1283,12 +1279,16 @@
             int hotseatBarTopPadding =
                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx;
 
-            // Push icons to the side
-            int requiredWidth = getHotseatRequiredWidth();
-            int hotseatWidth = Math.min(requiredWidth, availableWidthPx - hotseatBarEndOffset);
-            int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
+            int hotseatWidth = getHotseatRequiredWidth();
+            int leftSpacing = (availableWidthPx - hotseatWidth) / 2;
+            int rightSpacing = leftSpacing;
+            // Hotseat aligns to the left with nav buttons
+            if (hotseatBarEndOffset > 0) {
+                leftSpacing = inlineNavButtonsEndSpacing;
+                rightSpacing = availableWidthPx - hotseatWidth - leftSpacing + hotseatBorderSpace;
+            }
 
-            hotseatBarPadding.set(sideSpacing, hotseatBarTopPadding, sideSpacing,
+            hotseatBarPadding.set(leftSpacing, hotseatBarTopPadding, rightSpacing,
                     hotseatBarBottomPadding);
 
             boolean isRtl = Utilities.isRtl(context.getResources());
@@ -1297,14 +1297,6 @@
             } else {
                 hotseatBarPadding.left += getAdditionalQsbSpace();
             }
-
-            if (hotseatBarEndOffset > sideSpacing) {
-                int diff = isRtl
-                        ? sideSpacing - hotseatBarEndOffset
-                        : hotseatBarEndOffset - sideSpacing;
-                hotseatBarPadding.left -= diff;
-                hotseatBarPadding.right += diff;
-            }
         } else if (isScalableGrid) {
             int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
             hotseatBarPadding.set(sideSpacing,
@@ -1340,7 +1332,7 @@
     private int getHotseatRequiredWidth() {
         int additionalQsbSpace = getAdditionalQsbSpace();
         return iconSizePx * numShownHotseatIcons
-                + hotseatBorderSpace * (numShownHotseatIcons - 1)
+                + hotseatBorderSpace * (numShownHotseatIcons - (areNavButtonsInline ? 0 : 1))
                 + additionalQsbSpace;
     }
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 6f44375..ffe81ad 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -422,6 +422,21 @@
         }
         supportedProfiles = Collections.unmodifiableList(localSupportedProfiles);
 
+        int numMinShownHotseatIconsForTablet = supportedProfiles
+                .stream()
+                .filter(deviceProfile -> deviceProfile.isTablet)
+                .mapToInt(deviceProfile -> deviceProfile.numShownHotseatIcons)
+                .min()
+                .orElse(0);
+
+        supportedProfiles
+                .stream()
+                .filter(deviceProfile -> deviceProfile.isTablet)
+                .forEach(deviceProfile -> {
+                    deviceProfile.numShownHotseatIcons = numMinShownHotseatIconsForTablet;
+                    deviceProfile.recalculateHotseatWidthAndBorderSpace();
+                });
+
         ComponentName cn = new ComponentName(context.getPackageName(), getClass().getName());
         defaultWidgetPadding = AppWidgetHostView.getDefaultPaddingForWidget(context, cn, null);
     }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 18ed0fc..43772e4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1548,8 +1548,8 @@
     }
 
     protected LauncherWidgetHolder createAppWidgetHolder() {
-        return new LauncherWidgetHolder(this,
-                appWidgetId -> getWorkspace().removeWidget(appWidgetId));
+        return LauncherWidgetHolder.HolderFactory.newFactory(this).newInstance(
+                this, appWidgetId -> getWorkspace().removeWidget(appWidgetId));
     }
 
     public LauncherModel getModel() {
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index f4c0501..767c3d9 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -1093,7 +1093,7 @@
          */
         @NonNull
         public LauncherWidgetHolder newLauncherWidgetHolder() {
-            return new LauncherWidgetHolder(mContext);
+            return LauncherWidgetHolder.newInstance(mContext);
         }
 
         @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 9933ffb..63e6d13 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -203,8 +203,12 @@
 
         @Override
         public int getSpanSize(int position) {
-            int viewType = mApps.getAdapterItems().get(position).viewType;
             int totalSpans = mGridLayoutMgr.getSpanCount();
+            List<AdapterItem> items = mApps.getAdapterItems();
+            if (position >= items.size()) {
+                return totalSpans;
+            }
+            int viewType = items.get(position).viewType;
             if (isIconViewType(viewType)) {
                 return totalSpans / mAppsPerRow;
             } else {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index d1ada7a..74316e2 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -508,8 +508,12 @@
         if (grid.isVerticalBarLayout()) {
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
-            setPadding(grid.allAppsLeftRightMargin, grid.allAppsTopPadding,
-                    grid.allAppsLeftRightMargin, 0);
+            int topPadding = grid.allAppsTopPadding;
+            if (FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() && !grid.isTablet) {
+                topPadding += getResources().getDimensionPixelSize(
+                        R.dimen.all_apps_additional_top_padding_floating_search);
+            }
+            setPadding(grid.allAppsLeftRightMargin, topPadding, grid.allAppsLeftRightMargin, 0);
         }
 
         InsettableFrameLayout.dispatchInsets(this, insets);
@@ -824,7 +828,7 @@
         if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
             return;
         }
-        int bottom = getHeaderBottom();
+        int bottom = getHeaderBottom() + getVisibleContainerView().getPaddingTop();
         FloatingHeaderView headerView = getFloatingHeaderView();
         if (isTablet) {
             // Start adding header protection if search bar or tabs will attach to the top.
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 0e546ed..ce637ef 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -356,9 +356,6 @@
             "ENABLE_NEW_GESTURE_NAV_TUTORIAL", false,
             "Enable the redesigned gesture navigation tutorial");
 
-    public static final BooleanFlag ENABLE_TOAST_IMPRESSION_LOGGING = getDebugFlag(
-            "ENABLE_TOAST_IMPRESSION_LOGGING", false, "Enable toast impression logging");
-
     public static final BooleanFlag ENABLE_DEVICE_PROFILE_LOGGING = new DeviceFlag(
             "ENABLE_DEVICE_PROFILE_LOGGING", false, "Allows DeviceProfile logging");
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index a610548..4906c1d 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -295,7 +295,7 @@
         mWidgetCell.setRemoteViewsPreview(PinItemDragListener.getPreview(mRequest));
 
         mAppWidgetManager = new WidgetManagerHelper(this);
-        mAppWidgetHolder = new LauncherWidgetHolder(this);
+        mAppWidgetHolder = LauncherWidgetHolder.newInstance(this);
 
         PendingAddWidgetInfo pendingInfo =
                 new PendingAddWidgetInfo(widgetInfo, CONTAINER_PIN_WIDGETS);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index a45e835..5e97b2d 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -355,7 +355,7 @@
     private void restoreAppWidgetIdsIfExists(Context context) {
         SharedPreferences prefs = LauncherPrefs.getPrefs(context);
         if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
-            LauncherWidgetHolder holder = new LauncherWidgetHolder(context);
+            LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
             AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
                     IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
                     IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray(),
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 8f7a4ec..c499e35 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -32,7 +32,6 @@
 
 import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator;
-import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
@@ -292,11 +291,18 @@
                             ? mToState : mFromState;
             // snap to top or bottom using the release velocity
         } else {
-            float successTransitionProgress =
-                    mLauncher.getDeviceProfile().isTablet
-                            && (mToState == ALL_APPS || mFromState == ALL_APPS)
-                            ? TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS
-                            : SUCCESS_TRANSITION_PROGRESS;
+            float successTransitionProgress = SUCCESS_TRANSITION_PROGRESS;
+            if (mLauncher.getDeviceProfile().isTablet
+                    && (mToState == ALL_APPS || mFromState == ALL_APPS)) {
+                successTransitionProgress = TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
+            } else if (!mLauncher.getDeviceProfile().isTablet
+                    && mToState == ALL_APPS && mFromState == NORMAL) {
+                successTransitionProgress = AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
+            } else if (!mLauncher.getDeviceProfile().isTablet
+                    && mToState == NORMAL && mFromState == ALL_APPS) {
+                successTransitionProgress =
+                        1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
+            }
             targetState =
                     (interpolatedProgress > successTransitionProgress) ? mToState : mFromState;
         }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 5279dec..bfd0e1b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
 import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
@@ -64,11 +63,14 @@
 
     // ---- Custom interpolators for NORMAL -> ALL_APPS on phones only. ----
 
-    private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
-    private static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.305f;
-    private static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
-    private static final float ALL_APPS_FADE_END_ATOMIC = 0.4717f;
+    public static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.3333f;
+    public static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
+    private static final float ALL_APPS_FADE_END_ATOMIC = 0.8333f;
+    private static final float ALL_APPS_FADE_END_MANUAL = 0.8f;
     private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
+    private static final float SCRIM_FADE_START_ATOMIC = 0.2642f;
+    private static final float SCRIM_FADE_START_MANUAL = 0.117f;
+    private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
 
     private static final Interpolator LINEAR_EARLY_MANUAL =
             Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
@@ -98,27 +100,30 @@
     public static final Interpolator HOTSEAT_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
     public static final Interpolator HOTSEAT_FADE_MANUAL = STEP_TRANSITION_MANUAL;
 
-    public static final Interpolator HOTSEAT_SCALE_ATOMIC = STEP_TRANSITION_ATOMIC;
-    public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
-
-    public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC =
+    public static final Interpolator HOTSEAT_SCALE_ATOMIC =
             Interpolators.clampToProgress(
                     EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
                     ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+    public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC = STEP_TRANSITION_ATOMIC;
     public static final Interpolator HOTSEAT_TRANSLATE_MANUAL = STEP_TRANSITION_MANUAL;
 
     public static final Interpolator SCRIM_FADE_ATOMIC =
             Interpolators.clampToProgress(
                     Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
-                    WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
-    public static final Interpolator SCRIM_FADE_MANUAL = LINEAR_EARLY_MANUAL;
+                    SCRIM_FADE_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+    public static final Interpolator SCRIM_FADE_MANUAL =
+            Interpolators.clampToProgress(
+                    LINEAR, SCRIM_FADE_START_MANUAL, ALL_APPS_STATE_TRANSITION_MANUAL);
 
     public static final Interpolator ALL_APPS_FADE_ATOMIC =
             Interpolators.clampToProgress(
-                    Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1f),
+                    Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.2f, 1f),
                     ALL_APPS_STATE_TRANSITION_ATOMIC, ALL_APPS_FADE_END_ATOMIC);
     public static final Interpolator ALL_APPS_FADE_MANUAL =
-            Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+            Interpolators.clampToProgress(
+                    LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, ALL_APPS_FADE_END_MANUAL);
 
     public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_ATOMIC =
             Interpolators.clampToProgress(
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 5497729..d7235ad 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -19,7 +19,6 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
-import android.app.PendingIntent;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetManager;
@@ -29,7 +28,6 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.SparseArray;
-import android.view.View;
 import android.widget.RemoteViews;
 import android.widget.Toast;
 
@@ -45,7 +43,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.uioverrides.ApiWrapper;
+import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.function.IntConsumer;
@@ -86,11 +84,7 @@
     // TODO(b/191735836): Replace with SplashScreen.SPLASH_SCREEN_STYLE_EMPTY when un-hidden
     private static final int SPLASH_SCREEN_STYLE_EMPTY = 0;
 
-    public LauncherWidgetHolder(@NonNull Context context) {
-        this(context, null);
-    }
-
-    public LauncherWidgetHolder(@NonNull Context context,
+    protected LauncherWidgetHolder(@NonNull Context context,
             @Nullable IntConsumer appWidgetRemovedCallback) {
         mContext = context;
         mWidgetHost = createHost(context, appWidgetRemovedCallback);
@@ -321,15 +315,6 @@
     }
 
     /**
-     * Set the interaction handler for the widget host
-     * @param handler The interaction handler
-     */
-    public void setInteractionHandler(
-            @Nullable LauncherWidgetInteractionHandler handler) {
-        ApiWrapper.setHostInteractionHandler(mWidgetHost, handler);
-    }
-
-    /**
      * Delete the host
      */
     public void deleteHost() {
@@ -489,20 +474,35 @@
     }
 
     /**
-     * Set as a substitution for the hidden interaction handler in RemoteViews
+     * Returns the new LauncherWidgetHolder instance
      */
-    public interface LauncherWidgetInteractionHandler {
+    public static LauncherWidgetHolder newInstance(Context context) {
+        return HolderFactory.newFactory(context).newInstance(context, null);
+    }
+
+    /**
+     * A factory class that generates new instances of {@code LauncherWidgetHolder}
+     */
+    public static class HolderFactory implements ResourceBasedOverride {
+
         /**
-         * Invoked when the user performs an interaction on the View.
-         *
-         * @param view the View with which the user interacted
-         * @param pendingIntent the base PendingIntent associated with the view
-         * @param response the response to the interaction, which knows how to fill in the
-         *                 attached PendingIntent
+         * @param context The context of the caller
+         * @param appWidgetRemovedCallback The callback that is called when widgets are removed
+         * @return A new instance of {@code LauncherWidgetHolder}
          */
-        boolean onInteraction(
-                View view,
-                PendingIntent pendingIntent,
-                RemoteViews.RemoteResponse response);
+        public LauncherWidgetHolder newInstance(@NonNull Context context,
+                @Nullable IntConsumer appWidgetRemovedCallback) {
+            return new LauncherWidgetHolder(context, appWidgetRemovedCallback);
+        }
+
+        /**
+         * @param context The context of the caller
+         * @return A new instance of factory class for widget holders. If not specified, returning
+         * {@code HolderFactory} by default.
+         */
+        public static HolderFactory newFactory(Context context) {
+            return Overrides.getObject(
+                    HolderFactory.class, context, R.string.widget_holder_factory_class);
+        }
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
index 02f4ece..47bf135 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/ApiWrapper.java
@@ -17,14 +17,9 @@
 package com.android.launcher3.uioverrides;
 
 import android.app.Person;
-import android.appwidget.AppWidgetHost;
 import android.content.pm.ShortcutInfo;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.Utilities;
-import com.android.launcher3.widget.LauncherWidgetHolder;
 
 /**
  * A wrapper for the hidden API calls
@@ -36,14 +31,4 @@
     public static Person[] getPersons(ShortcutInfo si) {
         return Utilities.EMPTY_PERSON_ARRAY;
     }
-
-    /**
-     * Set the interaction handler for the host
-     * @param host AppWidgetHost that needs the interaction handler
-     * @param handler InteractionHandler for the views in the host
-     */
-    public static void setHostInteractionHandler(@NonNull AppWidgetHost host,
-            @Nullable LauncherWidgetHolder.LauncherWidgetInteractionHandler handler) {
-        // No-op
-    }
 }
diff --git a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
index 4e166ce..13e56f3 100644
--- a/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
+++ b/tests/src/com/android/launcher3/DeviceProfileBaseTest.kt
@@ -109,7 +109,7 @@
             numFolderColumns = 3
             folderStyle = R.style.FolderDefaultStyle
 
-            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_5
+            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_split
 
             horizontalMargin = FloatArray(4) { 22f }
 
@@ -277,7 +277,7 @@
             numFolderColumns = 3
             folderStyle = R.style.FolderDefaultStyle
 
-            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_4_4
+            inlineNavButtonsEndSpacing = R.dimen.taskbar_button_margin_split
 
             horizontalMargin = floatArrayOf(21.5f, 21.5f, 22.5f, 30.5f)
 
diff --git a/tests/src/com/android/launcher3/util/WidgetUtils.java b/tests/src/com/android/launcher3/util/WidgetUtils.java
index e514142..b0df055 100644
--- a/tests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/src/com/android/launcher3/util/WidgetUtils.java
@@ -70,7 +70,7 @@
             pendingInfo.minSpanY = item.minSpanY;
             Bundle options = pendingInfo.getDefaultSizeOptions(targetContext);
 
-            LauncherWidgetHolder holder = new LauncherWidgetHolder(targetContext);
+            LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(targetContext);
             try {
                 int widgetId = holder.allocateAppWidgetId();
                 if (!new WidgetManagerHelper(targetContext)