Merge "Two panel & two page swipe & currentPage fixes" into sc-dev
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 9944270..eed493d 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -42,6 +42,7 @@
 import android.app.prediction.AppTargetId;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
@@ -62,9 +63,11 @@
 import com.android.launcher3.logging.StatsLogManager.EventEnum;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shortcuts.ShortcutRequest;
 import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
 
 import java.util.Locale;
+import java.util.Optional;
 import java.util.function.ObjIntConsumer;
 import java.util.function.Predicate;
 
@@ -174,6 +177,7 @@
             return null;
         }
         ComponentName cn = null;
+        ShortcutInfo shortcutInfo = null;
         String id = null;
 
         switch (info.getItemCase()) {
@@ -188,6 +192,14 @@
                 LauncherAtom.Shortcut si = info.getShortcut();
                 if (!TextUtils.isEmpty(si.getShortcutId())
                         && (cn = parseNullable(si.getShortcutName())) != null) {
+                    Optional<ShortcutInfo> opt = new ShortcutRequest(mContext,
+                            userHandle).forPackage(cn.getPackageName(), si.getShortcutId()).query(
+                            ShortcutRequest.ALL).stream().findFirst();
+                    if (opt.isPresent()) {
+                        shortcutInfo = opt.get();
+                    } else {
+                        return null;
+                    }
                     id = "shortcut:" + si.getShortcutId();
                 }
                 break;
@@ -210,6 +222,9 @@
                 return createTempFolderTarget();
         }
         if (id != null && cn != null) {
+            if (shortcutInfo != null) {
+                return new AppTarget.Builder(new AppTargetId(id), shortcutInfo).build();
+            }
             return new AppTarget.Builder(new AppTargetId(id), cn.getPackageName(), userHandle)
                     .setClassName(cn.getClassName())
                     .build();
@@ -217,6 +232,7 @@
         return null;
     }
 
+
     private AppTarget createTempFolderTarget() {
         return new AppTarget.Builder(new AppTargetId("folder:" + SystemClock.uptimeMillis()),
                 mContext.getPackageName(), Process.myUserHandle())
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
index 744339b..3ae8581 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java
@@ -19,7 +19,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
+import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_TASKBAR;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REPLACE_TASKBAR_WITH_HOTSEAT;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -486,11 +486,15 @@
 
     private void replaceTaskbarWithHotseatOrViceVersa() {
         boolean replaceTaskbarWithHotseat = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
-                TYPE_ALL & TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
+                TYPE_REPLACE_TASKBAR_WITH_HOTSEAT) != null;
         if (!mLauncher.hasBeenResumed()) {
             replaceTaskbarWithHotseat = false;
         }
         setReplaceTaskbarWithHotseat(replaceTaskbarWithHotseat);
+
+        boolean hideTaskbar = AbstractFloatingView.getTopOpenViewWithType(mLauncher,
+                TYPE_HIDE_TASKBAR) != null;
+        mTaskbarVisibilityController.animateToVisibilityForFloatingView(hideTaskbar ? 0f : 1f);
     }
 
     private void setReplaceTaskbarWithHotseat(boolean replaceTaskbarWithHotseat) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
index 6d20d97..2228eba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarVisibilityController.java
@@ -31,6 +31,7 @@
 public class TaskbarVisibilityController {
 
     private static final long IME_VISIBILITY_ALPHA_DURATION = 120;
+    private static final long FLOATING_VIEW_VISIBILITY_ALPHA_DURATION = 120;
 
     private final BaseQuickstepLauncher mLauncher;
     private final TaskbarController.TaskbarVisibilityControllerCallbacks mTaskbarCallbacks;
@@ -44,6 +45,8 @@
             this::updateVisibilityAlpha);
     private AnimatedFloat mTaskbarVisibilityAlphaForIme = new AnimatedFloat(
             this::updateVisibilityAlpha);
+    private AnimatedFloat mTaskbarVisibilityAlphaForFloatingView = new AnimatedFloat(
+            this::updateVisibilityAlpha);
 
     public TaskbarVisibilityController(BaseQuickstepLauncher launcher,
             TaskbarController.TaskbarVisibilityControllerCallbacks taskbarCallbacks) {
@@ -59,12 +62,14 @@
         boolean isImeVisible = (SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags()
                 & QuickStepContract.SYSUI_STATE_IME_SHOWING) != 0;
         mTaskbarVisibilityAlphaForIme.updateValue(isImeVisible ? 0f : 1f);
+        mTaskbarVisibilityAlphaForFloatingView.updateValue(1f);
 
         onTaskbarBackgroundAlphaChanged();
         updateVisibilityAlpha();
     }
 
     protected void cleanup() {
+        setNavBarButtonAlpha(1f);
     }
 
     protected AnimatedFloat getTaskbarVisibilityForLauncherState() {
@@ -81,6 +86,11 @@
                 .setDuration(IME_VISIBILITY_ALPHA_DURATION).start();
     }
 
+    protected void animateToVisibilityForFloatingView(float toAlpha) {
+        mTaskbarVisibilityAlphaForIme.animateToValue(mTaskbarVisibilityAlphaForFloatingView.value,
+                toAlpha).setDuration(FLOATING_VIEW_VISIBILITY_ALPHA_DURATION).start();
+    }
+
     private void onTaskbarBackgroundAlphaChanged() {
         mTaskbarCallbacks.updateTaskbarBackgroundAlpha(mTaskbarBackgroundAlpha.value);
         updateVisibilityAlpha();
@@ -92,7 +102,16 @@
         // LauncherState if Launcher is paused.
         float alphaDueToLauncher = Math.max(mTaskbarBackgroundAlpha.value,
                 mTaskbarVisibilityAlphaForLauncherState.value);
-        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value;
-        mTaskbarCallbacks.updateTaskbarVisibilityAlpha(alphaDueToLauncher * alphaDueToOther);
+        float alphaDueToOther = mTaskbarVisibilityAlphaForIme.value
+                * mTaskbarVisibilityAlphaForFloatingView.value;
+        float taskbarAlpha = alphaDueToLauncher * alphaDueToOther;
+        mTaskbarCallbacks.updateTaskbarVisibilityAlpha(taskbarAlpha);
+
+        // Make the nav bar invisible if taskbar is visible.
+        setNavBarButtonAlpha(1f - taskbarAlpha);
+    }
+
+    private void setNavBarButtonAlpha(float navBarAlpha) {
+        SystemUiProxy.INSTANCE.get(mLauncher).setNavBarButtonAlpha(navBarAlpha, false);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index bedaefa..d65c59e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -76,8 +76,8 @@
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
         SCRIM_MULTIPLIER.set(scrim, 1f);
         getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
-        RECENTS_GRID_PROGRESS.set(mRecentsView, state.displayOverviewTasksAsGrid(mLauncher)
-                ? 1f : 0f);
+        RECENTS_GRID_PROGRESS.set(mRecentsView,
+                state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
     }
 
     @Override
@@ -128,7 +128,7 @@
                 toState.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
         setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
-                toState.displayOverviewTasksAsGrid(mLauncher) ? 1f : 0f, LINEAR);
+                toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f, LINEAR);
     }
 
     abstract FloatProperty getTaskModalnessProperty();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index d330a68..b4aa596 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -34,11 +34,11 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
@@ -85,8 +85,13 @@
     public void onDraw(Canvas canvas) {
         int count = canvas.save();
         if (!mIsPinned) {
-            boolean isBadged = getTag() instanceof WorkspaceItemInfo
-                    && !Process.myUserHandle().equals(((ItemInfo) getTag()).user);
+            boolean isBadged = false;
+            if (getTag() instanceof WorkspaceItemInfo) {
+                WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
+                isBadged = !Process.myUserHandle().equals(info.user)
+                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
+                        || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+            }
             drawEffect(canvas, isBadged);
             canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
             canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 2ad718b..fb58bf6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.quickstep.util.LayoutUtils;
@@ -76,7 +77,7 @@
     }
 
     @Override
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
         return false;
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 372784a..5a28cfd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -123,7 +123,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return displayOverviewTasksAsGrid(launcher) ? CLEAR_ALL_BUTTON
+        return displayOverviewTasksAsGrid(launcher.getDeviceProfile()) ? CLEAR_ALL_BUTTON
                 : CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
     }
 
@@ -133,8 +133,8 @@
     }
 
     @Override
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
-        return launcher.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 7df86b9..a2f15f5 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -80,9 +80,9 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.InputConsumerProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -124,14 +124,15 @@
  * Handles the navigation gestures when Launcher is the default home activity.
  */
 @TargetApi(Build.VERSION_CODES.R)
-public abstract class AbsSwipeUpHandler<T extends StatefulActivity<?>, Q extends RecentsView>
+public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
+        Q extends RecentsView, S extends BaseState<S>>
         extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
         RecentsAnimationCallbacks.RecentsAnimationListener {
     private static final String TAG = "AbsSwipeUpHandler";
 
     private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
 
-    protected final BaseActivityInterface<?, T> mActivityInterface;
+    protected final BaseActivityInterface<S, T> mActivityInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
     protected final ActivityInitListener mActivityInitListener;
     // Callbacks to be made once the recents animation starts
@@ -934,9 +935,15 @@
                     duration = Math.min(MAX_SWIPE_DURATION, 2 * baseDuration);
             }
         }
-        Interpolator interpolator =
-                endTarget == RECENTS ? (mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
-                        ? ACCEL_DEACCEL : OVERSHOOT_1_2) : DEACCEL;
+        Interpolator interpolator;
+        S state = mActivityInterface.stateFromGestureEndTarget(endTarget);
+        if (state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile())) {
+            interpolator = ACCEL_DEACCEL;
+        } else if (endTarget == RECENTS) {
+            interpolator = OVERSHOOT_1_2;
+        } else {
+            interpolator = DEACCEL;
+        }
 
         if (endTarget.isLauncher) {
             mInputConsumerProxy.enable();
@@ -1126,8 +1133,9 @@
                 }
             });
             animatorSet.play(windowAnim);
-            if (mRecentsView != null && mDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
-                    && mGestureState.getEndTarget() == RECENTS) {
+            S state = mActivityInterface.stateFromGestureEndTarget(mGestureState.getEndTarget());
+            if (mRecentsView != null && state.displayOverviewTasksAsGrid(
+                    mActivity.getDeviceProfile())) {
                 animatorSet.play(ObjectAnimator.ofFloat(mRecentsView, RECENTS_GRID_PROGRESS, 1));
                 animatorSet.play(mTaskViewSimulator.gridProgress.animateToValue(0, 1));
             }
@@ -1756,7 +1764,7 @@
 
     public interface Factory {
 
-        AbsSwipeUpHandler<StatefulActivity<?>, RecentsView> newHandler(
+        AbsSwipeUpHandler newHandler(
                 GestureState gestureState, long touchTimeMs, boolean continuingLastGesture);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 7c1d9fa..5942b3a 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -306,6 +306,11 @@
     public void onSystemUiFlagsChanged(int systemUiStateFlags) {
     }
 
+    /**
+     * Returns the expected STATE_TYPE from the provided GestureEndTarget.
+     */
+    public abstract STATE_TYPE stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget);
+
     public interface AnimationFactory {
 
         void createActivityInterface(long transitionLength);
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index db290d6..bffe3a1 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -18,6 +18,7 @@
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.fallback.RecentsState.BACKGROUND_APP;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
+import static com.android.quickstep.fallback.RecentsState.HOME;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -81,6 +82,7 @@
     @Override
     public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
+        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
         factory.initUI();
         return factory;
@@ -154,4 +156,25 @@
         }
         activity.<RecentsView>getOverviewPanel().startHome();
     }
+
+    @Override
+    public RecentsState stateFromGestureEndTarget(GestureState.GestureEndTarget endTarget) {
+        switch (endTarget) {
+            case RECENTS:
+                return DEFAULT;
+            case NEW_TASK:
+            case LAST_TASK:
+                return BACKGROUND_APP;
+            case HOME:
+            default:
+                return HOME;
+        }
+    }
+
+    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+        // reset layout on swipe to home
+        RecentsView recentsView = getCreatedActivity().getOverviewPanel();
+        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+                rotationTouchHelper.getDisplayRotation());
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index a80c111..7e4a352 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.TransformParams.BuilderProxy;
@@ -73,7 +74,7 @@
  */
 @TargetApi(Build.VERSION_CODES.R)
 public class FallbackSwipeHandler extends
-        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView> {
+        AbsSwipeUpHandler<RecentsActivity, FallbackRecentsView, RecentsState> {
 
     /**
      * Message used for receiving gesture nav contract information. We use a static messenger to
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 8d67ee6..ebdc1e6 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.os.Build;
 
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.tracing.GestureStateProto;
 import com.android.launcher3.tracing.SwipeHandlerProto;
@@ -213,7 +214,8 @@
     /**
      * @return the interface to the activity handing the UI updates for this gesture.
      */
-    public <T extends StatefulActivity<?>> BaseActivityInterface<?, T> getActivityInterface() {
+    public <S extends BaseState<S>,
+            T extends StatefulActivity<S>> BaseActivityInterface<S, T> getActivityInterface() {
         return mActivityInterface;
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 7efbfb8..ca5765e 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 
@@ -270,7 +271,7 @@
         if (taskbarController == null) {
             return;
         }
-        LauncherState toState = endTarget == GestureEndTarget.RECENTS ? OVERVIEW : NORMAL;
+        LauncherState toState = stateFromGestureEndTarget(endTarget);
         taskbarController.createAnimToLauncher(toState, duration).start();
     }
 
@@ -301,4 +302,18 @@
         }
         return taskbarController.isDraggingItem();
     }
+
+    @Override
+    public LauncherState stateFromGestureEndTarget(GestureEndTarget endTarget) {
+        switch (endTarget) {
+            case RECENTS:
+                return OVERVIEW;
+            case NEW_TASK:
+            case LAST_TASK:
+                return QUICK_SWITCH;
+            case HOME:
+            default:
+                return NORMAL;
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 842fb84..1ce4201 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -27,6 +27,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -39,7 +40,7 @@
  * Temporary class to allow easier refactoring
  */
 public class LauncherSwipeHandlerV2 extends
-        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView> {
+        AbsSwipeUpHandler<BaseQuickstepLauncher, RecentsView, LauncherState> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 619103c..a70cc4c 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -62,6 +62,7 @@
     private boolean mLastShelfVisible;
     private float mLastNavButtonAlpha;
     private boolean mLastNavButtonAnimate;
+    private boolean mHasNavButtonAlphaBeenSet = false;
 
     // TODO(141886704): Find a way to remove this
     private int mLastSystemUiStateFlags;
@@ -163,10 +164,12 @@
     @Override
     public void setNavBarButtonAlpha(float alpha, boolean animate) {
         boolean changed = Float.compare(alpha, mLastNavButtonAlpha) != 0
-                || animate != mLastNavButtonAnimate;
+                || animate != mLastNavButtonAnimate
+                || !mHasNavButtonAlphaBeenSet;
         if (mSystemUiProxy != null && changed) {
             mLastNavButtonAlpha = alpha;
             mLastNavButtonAnimate = animate;
+            mHasNavButtonAlphaBeenSet = true;
             try {
                 mSystemUiProxy.setNavBarButtonAlpha(alpha, animate);
             } catch (RemoteException e) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index 24a7610..54f6ce6 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_OFFSET;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
+import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
@@ -77,11 +78,12 @@
 
     private void setProperties(RecentsState state, StateAnimationConfig config,
             PropertySetter setter) {
-        float buttonAlpha = state.hasButtons() ? 1 : 0;
+        float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
         setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                buttonAlpha, LINEAR);
+                clearAllButtonAlpha, LINEAR);
+        float overviewButtonAlpha =  state.hasOverviewActions(mActivity) ? 1 : 0;
         setter.setFloat(mActivity.getActionsView().getVisibilityAlpha(),
-                MultiValueAlpha.VALUE, buttonAlpha, LINEAR);
+                MultiValueAlpha.VALUE, overviewButtonAlpha, LINEAR);
 
         float[] scaleAndOffset = state.getOverviewScaleAndOffset(mActivity);
         setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
@@ -94,5 +96,7 @@
         setter.setFloat(mRecentsView, TASK_MODALNESS, state.getOverviewModalness(),
                 config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
         setter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, state.isFullScreen() ? 1 : 0, LINEAR);
+        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
+                state.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()) ? 1f : 0f, LINEAR);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 02fd5bb..e075045 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -66,6 +66,7 @@
     @Override
     public void startHome() {
         mActivity.startHome();
+        mActivity.getStateManager().goToState(RecentsState.HOME);
     }
 
     /**
@@ -155,6 +156,11 @@
     }
 
     @Override
+    protected boolean isHomeTask(TaskView taskView) {
+        return mHomeTaskInfo != null && taskView.hasTaskId(mHomeTaskInfo.taskId);
+    }
+
+    @Override
     public void setModalStateEnabled(boolean isModalState) {
         super.setModalStateEnabled(isModalState);
         if (isModalState) {
@@ -169,6 +175,8 @@
     @Override
     public void onStateTransitionStart(RecentsState toState) {
         setOverviewStateEnabled(true);
+        setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+        setOverviewFullscreenEnabled(toState.isFullScreen());
         setFreezeViewVisibility(true);
     }
 
@@ -183,7 +191,7 @@
         super.setOverviewStateEnabled(enabled);
         if (enabled) {
             RecentsState state = mActivity.getStateManager().getState();
-            setDisallowScrollToClearAll(!state.hasButtons());
+            setDisallowScrollToClearAll(!state.hasClearAllButton());
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index f15a9de..a9856d2 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -20,6 +20,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.quickstep.RecentsActivity;
 
@@ -29,14 +31,19 @@
 public class RecentsState implements BaseState<RecentsState> {
 
     private static final int FLAG_MODAL = BaseState.getFlag(0);
-    private static final int FLAG_HAS_BUTTONS = BaseState.getFlag(1);
+    private static final int FLAG_CLEAR_ALL_BUTTON = BaseState.getFlag(1);
     private static final int FLAG_FULL_SCREEN = BaseState.getFlag(2);
+    private static final int FLAG_OVERVIEW_ACTIONS = BaseState.getFlag(3);
+    private static final int FLAG_SHOW_AS_GRID = BaseState.getFlag(4);
 
-    public static final RecentsState DEFAULT = new RecentsState(0, FLAG_HAS_BUTTONS);
+    public static final RecentsState DEFAULT = new RecentsState(0,
+            FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID);
     public static final RecentsState MODAL_TASK = new ModalState(1,
-            FLAG_DISABLE_RESTORE | FLAG_HAS_BUTTONS | FLAG_MODAL);
+            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+                    | FLAG_SHOW_AS_GRID);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN);
+    public static final RecentsState HOME = new RecentsState(3, 0);
 
     public final int ordinal;
     private final int mFlags;
@@ -82,14 +89,35 @@
         return hasFlag(FLAG_FULL_SCREEN);
     }
 
-    public boolean hasButtons() {
-        return hasFlag(FLAG_HAS_BUTTONS);
+    /**
+     * For this state, whether clear all button should be shown.
+     */
+    public boolean hasClearAllButton() {
+        return hasFlag(FLAG_CLEAR_ALL_BUTTON);
+    }
+
+    /**
+     * For this state, whether overview actions should be shown.
+     */
+    public boolean hasOverviewActions(RecentsActivity activity) {
+        return hasFlag(FLAG_OVERVIEW_ACTIONS) && !showAsGrid(activity.getDeviceProfile());
     }
 
     public float[] getOverviewScaleAndOffset(RecentsActivity activity) {
         return new float[] { NO_SCALE, NO_OFFSET };
     }
 
+    /**
+     * For this state, whether tasks should layout as a grid rather than a list.
+     */
+    public boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return hasFlag(FLAG_SHOW_AS_GRID) && showAsGrid(deviceProfile);
+    }
+
+    private boolean showAsGrid(DeviceProfile deviceProfile) {
+        return deviceProfile.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
+    }
+
     private static class ModalState extends RecentsState {
 
         public ModalState(int id, int flags) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index cee3363..fa9e0ec 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -24,6 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
@@ -38,7 +39,7 @@
 /**
  * Input consumer for handling touch on the recents/Launcher activity.
  */
-public class OverviewInputConsumer<T extends StatefulActivity<?>>
+public class OverviewInputConsumer<S extends BaseState<S>, T extends StatefulActivity<S>>
         implements InputConsumer {
 
     private final T mActivity;
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 6e8a5f1..8b5d498 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -271,10 +271,12 @@
             mSizeStrategy.calculateGridSize(mContext, mDp, mGridRect);
             mThumbnailData.rotation = mOrientationState.getDisplayRotation();
 
+            // mIsRecentsRtl is the inverse of TaskView RTL.
+            boolean isRtlEnabled = !mIsRecentsRtl;
             mPositionHelper.updateThumbnailMatrix(
                     mThumbnailPosition, mThumbnailData,
                     mTaskRect.width(), mTaskRect.height(),
-                    mDp, mOrientationState.getRecentsActivityRotation());
+                    mDp, mOrientationState.getRecentsActivityRotation(), isRtlEnabled);
             mPositionHelper.getMatrix().invert(mInversePositionMatrix);
 
             PagedOrientationHandler poh = mOrientationState.getOrientationHandler();
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 9d31190..da24b59 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -150,6 +150,8 @@
     @Override
     public void onStateTransitionStart(LauncherState toState) {
         setOverviewStateEnabled(toState.overviewUi);
+        setOverviewGridEnabled(toState.displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+        setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
         setFreezeViewVisibility(true);
     }
 
@@ -160,8 +162,6 @@
             reset();
         }
         setOverlayEnabled(finalState == OVERVIEW || finalState == OVERVIEW_MODAL_TASK);
-        setOverviewGridEnabled(finalState.displayOverviewTasksAsGrid(mActivity));
-        setOverviewFullscreenEnabled(finalState.getOverviewFullscreenProgress() == 1);
         setFreezeViewVisibility(false);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f5b62d5..8f35ea6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -337,7 +337,9 @@
     private float mTaskViewsPrimaryTranslation = 0;
     // Progress from 0 to 1 where 0 is a carousel and 1 is a 2 row grid.
     private float mGridProgress = 0;
-    private boolean mShowAsGrid;
+
+    // The GestureEndTarget that is still in progress.
+    private GestureState.GestureEndTarget mCurrentGestureEndTarget;
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -554,11 +556,6 @@
         mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
         mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
         mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
-
-        mShowAsGrid =
-                mActivity.getDeviceProfile().isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
-        mActivity.addOnDeviceProfileChangeListener(newDp ->
-                mShowAsGrid = newDp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get());
     }
 
     public OverScroller getScroller() {
@@ -731,7 +728,7 @@
     }
 
     public boolean isTaskViewVisible(TaskView tv) {
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int screenStart = mOrientationHandler.getPrimaryScroll(this);
             int screenEnd = screenStart + mOrientationHandler.getMeasuredSize(this);
             return isTaskViewWithinBounds(tv, screenStart, screenEnd);
@@ -743,9 +740,9 @@
 
     private boolean isTaskViewWithinBounds(TaskView tv, int start, int end) {
         int taskStart = mOrientationHandler.getChildStart(tv) + (int) tv.getOffsetAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled);
+                mOverviewFullscreenEnabled, showAsGrid());
         int taskSize = (int) (mOrientationHandler.getMeasuredSize(tv) * tv.getSizeAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled));
+                mOverviewFullscreenEnabled, showAsGrid()));
         int taskEnd = taskStart + taskSize;
         return (taskStart >= start && taskStart <= end) || (taskEnd >= start
                 && taskEnd <= end);
@@ -813,7 +810,7 @@
     public boolean onTouchEvent(MotionEvent ev) {
         super.onTouchEvent(ev);
 
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int taskCount = getTaskViewCount();
             for (int i = 0; i < taskCount; i++) {
                 TaskView taskView = getTaskViewAt(i);
@@ -880,7 +877,7 @@
 
     @Override
     protected boolean snapToPageInFreeScroll() {
-        return !mShowAsGrid;
+        return !showAsGrid();
     }
 
     @Override
@@ -1091,14 +1088,35 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
-        float accumulatedTranslationX = 0;
         final int taskCount = getTaskViewCount();
+        float accumulatedTranslationX = 0;
+        float[] fullscreenTranslations = new float[taskCount];
+        int firstNonHomeTaskIndex = 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
+            if (isHomeTask(taskView)) {
+                if (firstNonHomeTaskIndex == i) {
+                    firstNonHomeTaskIndex++;
+                }
+                continue;
+            }
+
             taskView.updateTaskSize();
-            taskView.setAccumulatedFullscreenTranslationX(accumulatedTranslationX);
-            accumulatedTranslationX += taskView.getFullscreenTranslationX();
+            fullscreenTranslations[i] += accumulatedTranslationX;
+            float widthDiff =
+                    taskView.getLayoutParams().width * (1 - taskView.getFullscreenScale());
+            float fullscreenTranslationX = mIsRtl ? widthDiff : -widthDiff;
+            fullscreenTranslations[i] += fullscreenTranslationX;
+            accumulatedTranslationX += fullscreenTranslationX;
         }
+
+        // We need to maintain first non-home task's full screen translation at 0, now shift
+        // translation of all the TaskViews to achieve that.
+        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+            getTaskViewAt(i).setFullscreenTranslationX(
+                    fullscreenTranslations[i] - fullscreenTranslations[firstNonHomeTaskIndex]);
+        }
+
         updateGridProperties();
     }
 
@@ -1204,7 +1222,7 @@
         int upper = 0;
         int visibleStart = 0;
         int visibleEnd = 0;
-        if (mShowAsGrid) {
+        if (showAsGrid()) {
             int screenStart = mOrientationHandler.getPrimaryScroll(this);
             int pageOrientedSize = mOrientationHandler.getMeasuredSize(this);
             int halfScreenSize = pageOrientedSize / 2;
@@ -1224,7 +1242,7 @@
             Task task = taskView.getTask();
             int index = indexOfChild(taskView);
             boolean visible;
-            if (mShowAsGrid) {
+            if (showAsGrid()) {
                 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd);
             } else {
                 visible = lower <= index && index <= upper;
@@ -1404,7 +1422,7 @@
      * Called when a gesture from an app has finished, and an end target has been determined.
      */
     public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-
+        mCurrentGestureEndTarget = endTarget;
     }
 
     /**
@@ -1425,9 +1443,11 @@
         animateUpRunningTaskIconScale();
 
         // TODO: This should be tied to whether there is a focus app on overview.
-        if (!mShowAsGrid) {
+        if (!showAsGrid()) {
             animateActionsViewIn();
         }
+
+        mCurrentGestureEndTarget = null;
     }
 
     /**
@@ -1601,11 +1621,19 @@
         float topAccumulatedTranslationX = 0;
         float bottomAccumulatedTranslationX = 0;
         IntSet topSet = new IntSet();
+        IntSet bottomSet = new IntSet();
         float[] gridTranslations = new float[taskCount];
+        int firstNonHomeTaskIndex = 0;
         for (int i = 0; i < taskCount; i++) {
             TaskView taskView = getTaskViewAt(i);
+            if (isHomeTask(taskView)) {
+                if (firstNonHomeTaskIndex == i) {
+                    firstNonHomeTaskIndex++;
+                }
+                continue;
+            }
+
             taskView.setGridScale(gridScale);
-            gridTranslations[i] = 0;
 
             float scaledWidth = taskView.getLayoutParams().width * gridScale;
             float taskGridHorizontalDiff;
@@ -1639,7 +1667,7 @@
 
                 // Move horizontally into empty space.
                 float widthOffset = 0;
-                for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
+                for (int j = i - 1; bottomSet.contains(j); j--) {
                     widthOffset += getTaskViewAt(j).getLayoutParams().width * gridScale
                             + mPageSpacing;
                 }
@@ -1650,6 +1678,7 @@
             } else {
                 gridTranslations[i] += bottomAccumulatedTranslationX;
                 bottomRowWidth += taskView.getLayoutParams().width * gridScale + mPageSpacing;
+                bottomSet.add(i);
 
                 // Move into bottom row.
                 float heightOffset = (boxLength + mTaskTopMargin) * gridScale + mRowSpacing;
@@ -1704,31 +1733,33 @@
                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
                         + clearAllShortTotalCompensation;
 
-        // We need to maintain first task's grid translation at 0, now shift translation of all
-        // the TaskViews to achieve that.
-        for (int i = 0; i < taskCount; i++) {
-            getTaskViewAt(i).setGridTranslationX(gridTranslations[i] - gridTranslations[0]);
+        // We need to maintain first non-home task's grid translation at 0, now shift translation
+        // of all the TaskViews to achieve that.
+        for (int i = firstNonHomeTaskIndex; i < taskCount; i++) {
+            getTaskViewAt(i).setGridTranslationX(
+                    gridTranslations[i] - gridTranslations[firstNonHomeTaskIndex]);
         }
-        mClearAllButton.setGridTranslationPrimary(clearAllTotalTranslationX - gridTranslations[0]);
+        mClearAllButton.setGridTranslationPrimary(
+                clearAllTotalTranslationX - gridTranslations[firstNonHomeTaskIndex]);
 
         setGridProgress(mGridProgress);
     }
 
+    protected boolean isHomeTask(TaskView taskView) {
+        return false;
+    }
+
     /**
      * Moves TaskView and ClearAllButton between carousel and 2 row grid.
      *
      * @param gridProgress 0 = carousel; 1 = 2 row grid.
      */
-    public void setGridProgress(float gridProgress) {
+    private void setGridProgress(float gridProgress) {
         int taskCount = getTaskViewCount();
         if (taskCount == 0) {
             return;
         }
 
-        if (!mShowAsGrid) {
-            gridProgress = 0;
-        }
-
         mGridProgress = gridProgress;
 
         for (int i = 0; i < taskCount; i++) {
@@ -1872,7 +1903,8 @@
                 if (animateTaskView) {
                     addDismissedTaskAnimations(taskView, duration, anim);
                 }
-            } else if (!mShowAsGrid) {  // Don't animate other tasks when dismissing in grid for now
+            } else if (!showAsGrid()) {
+                // For grid layout, don't animate other tasks when dismissing in grid for now.
                 // If we just take newScroll - oldScroll, everything to the right of dragged task
                 // translates to the left. We need to offset this in some cases:
                 // - In RTL, add page offset to all pages, since we want pages to move to the right
@@ -2865,9 +2897,9 @@
             float scrollDiff = 0;
             if (child instanceof TaskView) {
                 scrollDiff = ((TaskView) child).getScrollAdjustment(mOverviewFullscreenEnabled,
-                        mOverviewGridEnabled);
+                        showAsGrid());
             } else if (child instanceof ClearAllButton) {
-                scrollDiff = ((ClearAllButton) child).getScrollAdjustment(mOverviewGridEnabled);
+                scrollDiff = ((ClearAllButton) child).getScrollAdjustment(showAsGrid());
             }
 
             if (scrollDiff != 0) {
@@ -2884,9 +2916,9 @@
         View child = getChildAt(index);
         if (child instanceof TaskView) {
             childOffset += ((TaskView) child).getOffsetAdjustment(mOverviewFullscreenEnabled,
-                    mOverviewGridEnabled);
+                    showAsGrid());
         } else if (child instanceof ClearAllButton) {
-            childOffset += ((ClearAllButton) child).getOffsetAdjustment(mOverviewGridEnabled);
+            childOffset += ((ClearAllButton) child).getOffsetAdjustment(showAsGrid());
         }
         return childOffset;
     }
@@ -2898,7 +2930,7 @@
             return super.getChildVisibleSize(index);
         }
         return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
-                mOverviewFullscreenEnabled, mOverviewGridEnabled));
+                mOverviewFullscreenEnabled, showAsGrid()));
     }
 
     @Override
@@ -3109,6 +3141,12 @@
         return mSizeStrategy;
     }
 
+    private boolean showAsGrid() {
+        return mOverviewGridEnabled || (mCurrentGestureEndTarget != null
+                && mSizeStrategy.stateFromGestureEndTarget(
+                mCurrentGestureEndTarget).displayOverviewTasksAsGrid(mActivity.getDeviceProfile()));
+    }
+
     /**
      * Used to register callbacks for when our empty message state changes.
      *
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
index 36a5f03..17d075f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailView.java
@@ -379,9 +379,10 @@
                     mThumbnailData.thumbnail.getHeight());
             int currentRotation = getTaskView().getRecentsView().getPagedViewOrientedState()
                     .getRecentsActivityRotation();
+            boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
             mPreviewPositionHelper.updateThumbnailMatrix(mPreviewRect, mThumbnailData,
                     getMeasuredWidth(), getMeasuredHeight(), mActivity.getDeviceProfile(),
-                    currentRotation);
+                    currentRotation, isRtl);
 
             mBitmapShader.setLocalMatrix(mPreviewPositionHelper.mMatrix);
             mPaint.setShader(mBitmapShader);
@@ -466,7 +467,8 @@
          * Updates the matrix based on the provided parameters
          */
         public void updateThumbnailMatrix(Rect thumbnailBounds, ThumbnailData thumbnailData,
-                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation) {
+                int canvasWidth, int canvasHeight, DeviceProfile dp, int currentRotation,
+                boolean isRtl) {
             boolean isRotated = false;
             boolean isOrientationDifferent;
 
@@ -500,6 +502,17 @@
                 float availableHeight = surfaceHeight
                         - (thumbnailClipHint.top + thumbnailClipHint.bottom);
 
+                if (isRotated) {
+                    float canvasAspect = canvasWidth / (float) canvasHeight;
+                    float availableAspect = availableHeight / availableWidth;
+                    // Do not rotate thumbnail if it would not improve fit
+                    if (Utilities.isRelativePercentDifferenceGreaterThan(canvasAspect,
+                            availableAspect, 0.1f)) {
+                        isRotated = false;
+                        isOrientationDifferent = false;
+                    }
+                }
+
                 final float targetW, targetH;
                 if (isOrientationDifferent) {
                     targetW = canvasHeight;
@@ -535,28 +548,25 @@
                     }
                 }
 
-                // Update the clip hints
-                float halfExtraW = (availableWidth - croppedWidth) / 2;
-                thumbnailClipHint.left += halfExtraW;
-                thumbnailClipHint.right += halfExtraW;
-                if (thumbnailClipHint.left < 0) {
-                    thumbnailClipHint.right += thumbnailClipHint.left;
-                    thumbnailClipHint.left = 0;
-                } else if (thumbnailClipHint.right < 0) {
-                    thumbnailClipHint.left += thumbnailClipHint.right;
+                // Update the clip hints. Align to 0,0, crop the remaining.
+                if (isRtl) {
+                    if (thumbnailClipHint.right < 0) {
+                        thumbnailClipHint.left += thumbnailClipHint.right;
+                    }
                     thumbnailClipHint.right = 0;
+                    thumbnailClipHint.left += availableWidth - croppedWidth;
+                } else {
+                    if (thumbnailClipHint.left < 0) {
+                        thumbnailClipHint.right += thumbnailClipHint.left;
+                    }
+                    thumbnailClipHint.left = 0;
+                    thumbnailClipHint.right += availableWidth - croppedWidth;
                 }
-
-                float halfExtraH = (availableHeight - croppedHeight) / 2;
-                thumbnailClipHint.top += halfExtraH;
-                thumbnailClipHint.bottom += halfExtraH;
                 if (thumbnailClipHint.top < 0) {
                     thumbnailClipHint.bottom += thumbnailClipHint.top;
-                    thumbnailClipHint.top = 0;
-                } else if (thumbnailClipHint.bottom < 0) {
-                    thumbnailClipHint.top += thumbnailClipHint.bottom;
-                    thumbnailClipHint.bottom = 0;
                 }
+                thumbnailClipHint.top = 0;
+                thumbnailClipHint.bottom += availableHeight - croppedHeight;
 
                 thumbnailScale = targetW / (croppedWidth * scale);
             }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index cd8ea76..809adcb 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -27,8 +27,8 @@
 import static android.view.Surface.ROTATION_90;
 import static android.widget.Toast.LENGTH_SHORT;
 
-import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
+import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.comp;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
@@ -275,7 +275,6 @@
     private float mTaskResistanceTranslationY;
     // The following translation variables should only be used in the same orientation as Launcher.
     private float mFullscreenTranslationX;
-    private float mAccumulatedFullscreenTranslationX;
     private float mBoxTranslationY;
     // The following grid translations scales with mGridProgress.
     private float mGridTranslationX;
@@ -750,9 +749,8 @@
 
     @Override
     public void onRecycle() {
-        mFullscreenTranslationX = mAccumulatedFullscreenTranslationX = mGridTranslationX =
-                mGridTranslationY =
-                        mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
+        mFullscreenTranslationX = mGridTranslationX = mGridTranslationY =
+                mGridOffsetTranslationX = mBoxTranslationY = mNonRtlVisibleOffset = 0f;
         resetViewTransforms();
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
@@ -864,6 +862,10 @@
         applyScale();
     }
 
+    public float getFullscreenScale() {
+        return mFullscreenScale;
+    }
+
     public void setGridScale(float gridScale) {
         mGridScale = gridScale;
         applyScale();
@@ -921,20 +923,11 @@
         applyTranslationY();
     }
 
-    private void setFullscreenTranslationX(float fullscreenTranslationX) {
+    public void setFullscreenTranslationX(float fullscreenTranslationX) {
         mFullscreenTranslationX = fullscreenTranslationX;
         applyTranslationX();
     }
 
-    public float getFullscreenTranslationX() {
-        return mFullscreenTranslationX;
-    }
-
-    public void setAccumulatedFullscreenTranslationX(float accumulatedFullscreenTranslationX) {
-        mAccumulatedFullscreenTranslationX = accumulatedFullscreenTranslationX;
-        applyTranslationX();
-    }
-
     public void setGridTranslationX(float gridTranslationX) {
         mGridTranslationX = gridTranslationX;
         applyTranslationX();
@@ -965,7 +958,7 @@
     public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
         float scrollAdjustment = 0;
         if (fullscreenEnabled) {
-            scrollAdjustment += mFullscreenTranslationX + mAccumulatedFullscreenTranslationX;
+            scrollAdjustment += mFullscreenTranslationX;
         }
         if (gridEnabled) {
             scrollAdjustment += mGridTranslationX;
@@ -999,7 +992,7 @@
 
     private void applyTranslationX() {
         setTranslationX(mDismissTranslationX + mTaskOffsetTranslationX + mTaskResistanceTranslationX
-                + getFullscreenTrans(mFullscreenTranslationX + mAccumulatedFullscreenTranslationX)
+                + getFullscreenTrans(mFullscreenTranslationX)
                 + getGridTrans(mGridTranslationX + mGridOffsetTranslationX));
     }
 
@@ -1243,10 +1236,6 @@
             }
             setFullscreenScale(fullscreenScale);
 
-            float widthDiff = params.width * (1 - mFullscreenScale);
-            setFullscreenTranslationX(
-                    getLayoutDirection() == LAYOUT_DIRECTION_RTL ? -widthDiff : widthDiff);
-
             if (params.width != expectedWidth || params.height != expectedHeight) {
                 params.width = expectedWidth;
                 params.height = expectedHeight;
@@ -1254,7 +1243,6 @@
             }
         } else {
             setBoxTranslationY(0);
-            setFullscreenTranslationX(0);
             setFullscreenScale(1);
             if (params.width != ViewGroup.LayoutParams.MATCH_PARENT) {
                 params.width = ViewGroup.LayoutParams.MATCH_PARENT;
diff --git a/res/layout/app_widget_resize_frame.xml b/res/layout/app_widget_resize_frame.xml
index dfce946..2e476df 100644
--- a/res/layout/app_widget_resize_frame.xml
+++ b/res/layout/app_widget_resize_frame.xml
@@ -34,6 +34,7 @@
 
         <!-- Left -->
         <ImageView
+            android:id="@+id/widget_resize_left_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="left|center_vertical"
@@ -43,6 +44,7 @@
 
         <!-- Top -->
         <ImageView
+            android:id="@+id/widget_resize_top_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="top|center_horizontal"
@@ -52,6 +54,7 @@
 
         <!-- Right -->
         <ImageView
+            android:id="@+id/widget_resize_right_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="right|center_vertical"
@@ -61,6 +64,7 @@
 
         <!-- Bottom -->
         <ImageView
+            android:id="@+id/widget_resize_bottom_handle"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="bottom|center_horizontal"
diff --git a/res/layout/widgets_search_bar.xml b/res/layout/widgets_search_bar.xml
index 252637d..450e5f1 100644
--- a/res/layout/widgets_search_bar.xml
+++ b/res/layout/widgets_search_bar.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.android.launcher3.widget.picker.search.WidgetsSearchBar
+<com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/widgets_search_bar"
     android:layout_width="match_parent"
@@ -7,8 +7,7 @@
     android:orientation="horizontal"
     android:layout_marginTop="16dp"
     android:background="@drawable/bg_widgets_searchbox"
-    android:padding="12dp"
-    android:visibility="gone">
+    android:padding="12dp">
 
     <EditText
         android:id="@+id/widgets_search_bar_edit_text"
@@ -30,4 +29,4 @@
         android:background="?android:selectableItemBackground"
         android:layout_gravity="center"
         android:visibility="gone"/>
-</com.android.launcher3.widget.picker.search.WidgetsSearchBar>
\ No newline at end of file
+</com.android.launcher3.widget.picker.search.LauncherWidgetsSearchBar>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index e263c7a..d894bb4 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -101,6 +101,10 @@
     // When these types of floating views are open, hide the taskbar hotseat and show the real one.
     public static final int TYPE_REPLACE_TASKBAR_WITH_HOTSEAT = TYPE_FOLDER | TYPE_ACTION_POPUP;
 
+    // Hide the taskbar when these types of floating views are open.
+    public static final int TYPE_HIDE_TASKBAR = TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGETS_FULL_SHEET
+            | TYPE_ON_BOARD_POPUP;
+
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU;
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 9d6af9f..8071782 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -20,7 +20,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewGroup;
 
 import androidx.annotation.Nullable;
 
@@ -139,10 +138,10 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        ViewGroup content = (ViewGroup) getChildAt(0);
-        for (int i = 0; i < HANDLE_COUNT; i ++) {
-            mDragHandles[i] = content.getChildAt(i);
-        }
+        mDragHandles[INDEX_LEFT] = findViewById(R.id.widget_resize_left_handle);
+        mDragHandles[INDEX_TOP] = findViewById(R.id.widget_resize_top_handle);
+        mDragHandles[INDEX_RIGHT] = findViewById(R.id.widget_resize_right_handle);
+        mDragHandles[INDEX_BOTTOM] = findViewById(R.id.widget_resize_bottom_handle);
     }
 
     @Override
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 02c6162..c79dabe 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -131,10 +131,9 @@
     public void reset() {
         if (!TextUtils.isEmpty(getText())) {
             setText("");
-        } else {
-            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                return;
-            }
+        }
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
+            return;
         }
         if (isFocused()) {
             View nextFocus = focusSearch(View.FOCUS_DOWN);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index aa97450..ae75b51 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -238,13 +238,6 @@
     }
 
     /**
-     * For this state, whether tasks should layout as a grid rather than a list.
-     */
-    public boolean displayOverviewTasksAsGrid(Launcher launcher) {
-        return false;
-    }
-
-    /**
      * For this state, how much additional vertical translation there should be for each of the
      * child TaskViews.
      */
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index c440303..94c6574 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -690,6 +690,16 @@
         };
     }
 
+    /**
+     * Compares the ratio of two quantities and returns whether that ratio is greater than the
+     * provided bound. Order of quantities does not matter. Bound should be a decimal representation
+     * of a percentage.
+     */
+    public static boolean isRelativePercentDifferenceGreaterThan(float first, float second,
+            float bound) {
+        return (Math.abs(first - second) / Math.abs((first + second) / 2.0f)) > bound;
+    }
+
     private static class FixedSizeEmptyDrawable extends ColorDrawable {
 
         private final int mSize;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5aea45a..a94fab7 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -69,6 +69,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -418,15 +419,6 @@
             layout.markCellsAsUnoccupiedForView(mDragInfo.cell);
         }
 
-        if (mOutlineProvider != null) {
-            if (dragObject.dragView != null) {
-                Bitmap preview = dragObject.dragView.getPreviewBitmap();
-
-                // The outline is used to visualize where the item will land if dropped
-                mOutlineProvider.generateDragOutline(preview);
-            }
-        }
-
         updateChildrenLayersEnabled();
 
         // Do not add a new page if it is a accessible drag which was not started by the workspace.
@@ -1535,10 +1527,10 @@
             draggableView = (DraggableView) child;
         }
 
-        // The drag bitmap follows the touch point around on the screen
-        final Bitmap b = previewProvider.createDragBitmap();
+        // The draggable drawable follows the touch point around on the screen
+        final Drawable drawable = previewProvider.createDrawable();
         int halfPadding = previewProvider.previewPadding / 2;
-        float scale = previewProvider.getScaleAndPosition(b, mTempXY);
+        float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
         int dragLayerX = mTempXY[0];
         int dragLayerY = mTempXY[1];
 
@@ -1564,9 +1556,18 @@
             }
         }
 
-        DragView dv = mDragController.startDrag(b, draggableView, dragLayerX, dragLayerY, source,
-                dragObject, dragVisualizeOffset, dragRect, scale * iconScale,
-                scale, dragOptions);
+        DragView dv = mDragController.startDrag(
+                drawable,
+                draggableView,
+                dragLayerX,
+                dragLayerY,
+                source,
+                dragObject,
+                dragVisualizeOffset,
+                dragRect,
+                scale * iconScale,
+                scale,
+                dragOptions);
         dv.setIntrinsicIconScaleFactor(dragOptions.intrinsicIconScaleFactor);
         return dv;
     }
@@ -2622,7 +2623,11 @@
 
     }
 
-    public Bitmap createWidgetBitmap(ItemInfo widgetInfo, View layout) {
+    private Drawable createWidgetDrawable(ItemInfo widgetInfo, View layout) {
+        if (layout instanceof LauncherAppWidgetHostView) {
+            return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) layout);
+        }
+
         int[] unScaledSize = estimateItemSize(widgetInfo);
         int visibility = layout.getVisibility();
         layout.setVisibility(VISIBLE);
@@ -2634,7 +2639,7 @@
         Bitmap b = BitmapRenderer.createHardwareBitmap(
                 unScaledSize[0], unScaledSize[1], layout::draw);
         layout.setVisibility(visibility);
-        return b;
+        return new FastBitmapDrawable(b);
     }
 
     private void getFinalPositionForDropAnimation(int[] loc, float[] scaleXY,
@@ -2704,8 +2709,8 @@
         boolean isWidget = info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET ||
                 info.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
         if ((animationType == ANIMATE_INTO_POSITION_AND_RESIZE || external) && finalView != null) {
-            Bitmap crossFadeBitmap = createWidgetBitmap(info, finalView);
-            dragView.setCrossFadeBitmap(crossFadeBitmap);
+            Drawable crossFadeDrawable = createWidgetDrawable(info, finalView);
+            dragView.setCrossFadeDrawable(crossFadeDrawable);
             dragView.crossFade((int) (duration * 0.8f));
         } else if (isWidget && external) {
             scaleXY[0] = scaleXY[1] = Math.min(scaleXY[0],  scaleXY[1]);
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4c1f19d..fdc69ec 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -246,11 +246,7 @@
             hideInput();
             return false;
         }
-        boolean shouldScroll = rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
-        if (shouldScroll) {
-            hideInput();
-        }
-        return shouldScroll;
+        return rv.shouldContainerScroll(ev, mLauncher.getDragLayer());
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 179cb77..f307a53 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -31,6 +31,7 @@
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.WindowInsets;
 
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -188,6 +189,7 @@
             case SCROLL_STATE_DRAGGING:
                 mgr.logger().sendToInteractionJankMonitor(
                         LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN, this);
+                getWindowInsetsController().hide(WindowInsets.Type.ime());
                 break;
             case SCROLL_STATE_IDLE:
                 mgr.logger().sendToInteractionJankMonitor(
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index abf63dc..1e6f829 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -246,6 +246,7 @@
      * TODO: This logic should go in {@link LauncherState}
      */
     private void onProgressAnimationEnd() {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
         if (Float.compare(mProgress, 1f) == 0) {
             mAppsView.reset(false /* animate */);
         }
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 3319018..d3c9993 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -144,7 +144,7 @@
 
     @Override
     public void onFocusChange(View view, boolean hasFocus) {
-        if (!hasFocus) {
+        if (!hasFocus && !FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
             mInput.hideKeyboard();
         }
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 43123c1..96251f0 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -140,6 +140,9 @@
     public static final BooleanFlag ENABLE_OVERVIEW_SELECTIONS = new DeviceFlag(
             "ENABLE_OVERVIEW_SELECTIONS", true, "Show Select Mode button in Overview Actions");
 
+    public static final BooleanFlag ENABLE_WIDGETS_PICKER_AIAI_SEARCH = new DeviceFlag(
+            "ENABLE_WIDGETS_PICKER_AIAI_SEARCH", false, "Enable AiAi search in the widgets picker");
+
     public static final BooleanFlag ENABLE_OVERVIEW_SHARE = getDebugFlag(
             "ENABLE_OVERVIEW_SHARE", false, "Show Share button in Overview Actions");
 
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index c972cbb..7bc9865 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -142,7 +142,7 @@
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (img.getBitmap() == null) {
+        if (img.getDrawable() == null) {
             return false;
         }
 
@@ -151,7 +151,7 @@
 
         // Start home and pass the draw request params
         PinItemDragListener listener = new PinItemDragListener(mRequest, bounds,
-                img.getBitmap().getWidth(), img.getWidth());
+                img.getDrawable().getIntrinsicWidth(), img.getWidth());
 
 
         // Start a system drag and drop. We use a transparent bitmap as preview for system drag
diff --git a/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
new file mode 100644
index 0000000..477bc6e
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/AppWidgetHostViewDrawable.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.dragndrop;
+
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
+
+/** A drawable which renders {@link LauncherAppWidgetHostView} to a canvas. */
+public final class AppWidgetHostViewDrawable extends Drawable {
+
+    private final LauncherAppWidgetHostView mAppWidgetHostView;
+    private Paint mPaint = new Paint();
+
+    public AppWidgetHostViewDrawable(LauncherAppWidgetHostView appWidgetHostView) {
+        mAppWidgetHostView = appWidgetHostView;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.saveLayer(0, 0, getIntrinsicWidth(), getIntrinsicHeight(), mPaint);
+        mAppWidgetHostView.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mAppWidgetHostView.getMeasuredWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mAppWidgetHostView.getMeasuredHeight();
+    }
+
+    @Override
+    public int getOpacity() {
+        // This is up to app widget provider. We don't know if the host view will cover anything
+        // behind the drawable.
+        return PixelFormat.UNKNOWN;
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mPaint.setAlpha(alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mPaint.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter colorFilter) {
+        mPaint.setColorFilter(colorFilter);
+    }
+
+    @Override
+    public ColorFilter getColorFilter() {
+        return mPaint.getColorFilter();
+    }
+}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 93df599..b7a70cb 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -25,9 +25,9 @@
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.view.DragEvent;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -129,7 +129,7 @@
      * drop, it is the responsibility of the {@link DropTarget} to exit out of the spring loaded
      * mode. If the drop was cancelled for some reason, the UI will automatically exit out of this mode.
      *
-     * @param b The bitmap to display as the drag image.  It will be re-scaled to the
+     * @param drawable The drawable to be displayed in the drag view.  It will be re-scaled to the
      *          enlarged size.
      * @param originalView The source view (ie. icon, widget etc.) that is being dragged
      *          and which the DragView represents
@@ -140,9 +140,18 @@
      * @param dragRegion Coordinates within the bitmap b for the position of item being dragged.
      *          Makes dragging feel more precise, e.g. you can clip out a transparent border
      */
-    public DragView startDrag(Bitmap b, DraggableView originalView, int dragLayerX, int dragLayerY,
-            DragSource source, ItemInfo dragInfo, Point dragOffset, Rect dragRegion,
-            float initialDragViewScale, float dragViewScaleOnDrop, DragOptions options) {
+    public DragView startDrag(
+            Drawable drawable,
+            DraggableView originalView,
+            int dragLayerX,
+            int dragLayerY,
+            DragSource source,
+            ItemInfo dragInfo,
+            Point dragOffset,
+            Rect dragRegion,
+            float initialDragViewScale,
+            float dragViewScaleOnDrop,
+            DragOptions options) {
         if (PROFILE_DRAWING_DURING_DRAG) {
             android.os.Debug.startMethodTracing("Launcher");
         }
@@ -173,8 +182,14 @@
         final Resources res = mLauncher.getResources();
         final float scaleDps = mIsInPreDrag
                 ? res.getDimensionPixelSize(R.dimen.pre_drag_view_scale) : 0f;
-        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
-                registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
+        final DragView dragView = mDragObject.dragView = new DragView(
+                mLauncher,
+                drawable,
+                registrationX,
+                registrationY,
+                initialDragViewScale,
+                dragViewScaleOnDrop,
+                scaleDps);
         dragView.setItemInfo(dragInfo);
         mDragObject.dragComplete = false;
 
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 86b93d0..df4d811 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -67,9 +67,9 @@
     public static final int COLOR_CHANGE_DURATION = 120;
     public static final int VIEW_ZOOM_DURATION = 150;
 
-    private boolean mDrawBitmap = true;
-    private Bitmap mBitmap;
-    private Bitmap mCrossFadeBitmap;
+    private boolean mShouldDraw = true;
+    private Drawable mDrawable;
+    private Drawable mCrossFadeDrawable;
     @Thunk Paint mPaint;
     private final int mBlurSizeOutline;
     private final int mRegistrationX;
@@ -114,19 +114,21 @@
      * The registration point is the point inside our view that the touch events should
      * be centered upon.
      * @param launcher The Launcher instance
-     * @param bitmap The view that we're dragging around.  We scale it up when we draw it.
+     * @param drawable The view that we're dragging around.  We scale it up when we draw it.
      * @param registrationX The x coordinate of the registration point.
      * @param registrationY The y coordinate of the registration point.
      */
-    public DragView(Launcher launcher, Bitmap bitmap, int registrationX, int registrationY,
-                    final float initialScale, final float scaleOnDrop, final float finalScaleDps) {
+    public DragView(Launcher launcher, Drawable drawable, int registrationX,
+            int registrationY, final float initialScale, final float scaleOnDrop,
+            final float finalScaleDps) {
         super(launcher);
         mLauncher = launcher;
         mDragLayer = launcher.getDragLayer();
         mDragController = launcher.getDragController();
         mFirstFrameAnimatorHelper = new FirstFrameAnimatorHelper(this);
 
-        final float scale = (bitmap.getWidth() + finalScaleDps) / bitmap.getWidth();
+        final float scale = (drawable.getIntrinsicWidth() + finalScaleDps)
+                / drawable.getIntrinsicWidth();
 
         // Set the initial scale to avoid any jumps
         setScaleX(initialScale);
@@ -144,8 +146,9 @@
             }
         });
 
-        mBitmap = bitmap;
-        setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
+        mDrawable = drawable;
+        setDragRegion(new Rect(0, 0, drawable.getIntrinsicWidth(),
+                drawable.getIntrinsicHeight()));
 
         // The point in our scaled bitmap that the touch events are located
         mRegistrationX = registrationX;
@@ -197,8 +200,8 @@
             @Override
             public void run() {
                 Object[] outObj = new Object[1];
-                int w = mBitmap.getWidth();
-                int h = mBitmap.getHeight();
+                int w = mDrawable.getIntrinsicWidth();
+                int h = mDrawable.getIntrinsicHeight();
                 Drawable dr = Utilities.getFullDrawable(mLauncher, info, w, h, outObj);
 
                 if (dr instanceof AdaptiveIconDrawable) {
@@ -214,11 +217,11 @@
                     mBadge.setBounds(badgeBounds);
 
                     // Do not draw the background in case of folder as its translucent
-                    mDrawBitmap = !(dr instanceof FolderAdaptiveIcon);
+                    mShouldDraw = !(dr instanceof FolderAdaptiveIcon);
 
                     try (LauncherIcons li = LauncherIcons.obtain(mLauncher)) {
                         Drawable nDr; // drawable to be normalized
-                        if (mDrawBitmap) {
+                        if (mShouldDraw) {
                             nDr = dr;
                         } else {
                             // Since we just want the scale, avoid heavy drawing operations
@@ -308,7 +311,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        setMeasuredDimension(mBitmap.getWidth(), mBitmap.getHeight());
+        setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
     }
 
     /** Sets the scale of the view over the normal workspace icon size. */
@@ -352,29 +355,37 @@
         return mDragRegion;
     }
 
-    public Bitmap getPreviewBitmap() {
-        return mBitmap;
-    }
-
     @Override
     protected void onDraw(Canvas canvas) {
         mHasDrawn = true;
 
-        if (mDrawBitmap) {
+        if (mShouldDraw) {
             // Always draw the bitmap to mask anti aliasing due to clipPath
-            boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeBitmap != null;
+            boolean crossFade = mCrossFadeProgress > 0 && mCrossFadeDrawable != null;
             if (crossFade) {
                 int alpha = crossFade ? (int) (255 * (1 - mCrossFadeProgress)) : 255;
                 mPaint.setAlpha(alpha);
             }
-            canvas.drawBitmap(mBitmap, 0.0f, 0.0f, mPaint);
+            mDrawable.setColorFilter(mPaint.getColorFilter());
+            mDrawable.setAlpha(mPaint.getAlpha());
+            mDrawable.setBounds(
+                    new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+                            mDrawable.getIntrinsicHeight()));
+            mDrawable.draw(canvas);
             if (crossFade) {
                 mPaint.setAlpha((int) (255 * mCrossFadeProgress));
                 final int saveCount = canvas.save();
-                float sX = (mBitmap.getWidth() * 1.0f) / mCrossFadeBitmap.getWidth();
-                float sY = (mBitmap.getHeight() * 1.0f) / mCrossFadeBitmap.getHeight();
+                float sX = ((float) mDrawable.getIntrinsicWidth())
+                        / mCrossFadeDrawable.getIntrinsicWidth();
+                float sY = ((float) mDrawable.getIntrinsicHeight())
+                        / mCrossFadeDrawable.getIntrinsicHeight();
                 canvas.scale(sX, sY);
-                canvas.drawBitmap(mCrossFadeBitmap, 0.0f, 0.0f, mPaint);
+                mCrossFadeDrawable.setColorFilter(mPaint.getColorFilter());
+                mCrossFadeDrawable.setAlpha(mPaint.getAlpha());
+                mDrawable.setBounds(
+                        new Rect(0, 0, mDrawable.getIntrinsicWidth(),
+                                mDrawable.getIntrinsicHeight()));
+                mCrossFadeDrawable.draw(canvas);
                 canvas.restoreToCount(saveCount);
             }
         }
@@ -390,8 +401,8 @@
         }
     }
 
-    public void setCrossFadeBitmap(Bitmap crossFadeBitmap) {
-        mCrossFadeBitmap = crossFadeBitmap;
+    public void setCrossFadeDrawable(Drawable crossFadeDrawable) {
+        mCrossFadeDrawable = crossFadeDrawable;
     }
 
     public void crossFade(int duration) {
@@ -469,8 +480,8 @@
 
         // Start the pick-up animation
         DragLayer.LayoutParams lp = new DragLayer.LayoutParams(0, 0);
-        lp.width = mBitmap.getWidth();
-        lp.height = mBitmap.getHeight();
+        lp.width = mDrawable.getIntrinsicWidth();
+        lp.height = mDrawable.getIntrinsicHeight();
         lp.customPosition = true;
         setLayoutParams(lp);
         move(touchX, touchY);
diff --git a/src/com/android/launcher3/dragndrop/PinItemDragListener.java b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
index 2290473..9f12e6e 100644
--- a/src/com/android/launcher3/dragndrop/PinItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/PinItemDragListener.java
@@ -96,7 +96,7 @@
 
         PendingItemDragHelper dragHelper = new PendingItemDragHelper(view);
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_APPWIDGET) {
-            dragHelper.setPreview(getPreview(mRequest));
+            dragHelper.setRemoteViewsPreview(getPreview(mRequest));
         }
         return dragHelper;
     }
diff --git a/src/com/android/launcher3/graphics/DragPreviewProvider.java b/src/com/android/launcher3/graphics/DragPreviewProvider.java
index 21822a3..9bc5444 100644
--- a/src/com/android/launcher3/graphics/DragPreviewProvider.java
+++ b/src/com/android/launcher3/graphics/DragPreviewProvider.java
@@ -30,9 +30,11 @@
 import android.view.View;
 
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.SafeCloseable;
@@ -88,10 +90,14 @@
     }
 
     /**
-     * Returns a new bitmap to show when the {@link #mView} is being dragged around.
-     * Responsibility for the bitmap is transferred to the caller.
+     * Returns a new drawable to show when the {@link #mView} is being dragged around.
+     * Responsibility for the drawable is transferred to the caller.
      */
-    public Bitmap createDragBitmap() {
+    public Drawable createDrawable() {
+        if (mView instanceof LauncherAppWidgetHostView) {
+            return new AppWidgetHostViewDrawable((LauncherAppWidgetHostView) mView);
+        }
+
         int width = 0;
         int height = 0;
         // Assume scaleX == scaleY, which is always the case for workspace items.
@@ -105,8 +111,9 @@
             height = mView.getHeight();
         }
 
-        return BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
-                height + blurSizeOutline, (c) -> drawDragView(c, scale));
+        return new FastBitmapDrawable(
+                BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
+                        height + blurSizeOutline, (c) -> drawDragView(c, scale)));
     }
 
     public final void generateDragOutline(Bitmap preview) {
@@ -129,7 +136,7 @@
         return bounds;
     }
 
-    public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+    public float getScaleAndPosition(Drawable preview, int[] outPos) {
         float scale = Launcher.getLauncher(mView.getContext())
                 .getDragLayer().getLocationInDragLayer(mView, outPos);
         if (mView instanceof LauncherAppWidgetHostView) {
@@ -139,8 +146,8 @@
         }
 
         outPos[0] = Math.round(outPos[0] -
-                (preview.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
-        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getHeight() / 2
+                (preview.getIntrinsicWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
+        outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getIntrinsicHeight() / 2
                 - previewPadding / 2);
         return scale;
     }
diff --git a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
index 3e59b61..530aaed 100644
--- a/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
+++ b/src/com/android/launcher3/shortcuts/ShortcutDragPreviewProvider.java
@@ -23,6 +23,7 @@
 import android.graphics.drawable.Drawable;
 import android.view.View;
 
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -42,15 +43,16 @@
     }
 
     @Override
-    public Bitmap createDragBitmap() {
+    public Drawable createDrawable() {
         if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
             int size = Launcher.getLauncher(mView.getContext()).getDeviceProfile().iconSizePx;
-            return BitmapRenderer.createHardwareBitmap(
-                    size + blurSizeOutline,
-                    size + blurSizeOutline,
-                    (c) -> drawDragViewOnBackground(c, size));
+            return new FastBitmapDrawable(
+                    BitmapRenderer.createHardwareBitmap(
+                            size + blurSizeOutline,
+                            size + blurSizeOutline,
+                            (c) -> drawDragViewOnBackground(c, size)));
         } else {
-            return createDragBitmapLegacy();
+            return new FastBitmapDrawable(createDragBitmapLegacy());
         }
     }
 
@@ -81,7 +83,7 @@
     }
 
     @Override
-    public float getScaleAndPosition(Bitmap preview, int[] outPos) {
+    public float getScaleAndPosition(Drawable preview, int[] outPos) {
         Launcher launcher = Launcher.getLauncher(mView.getContext());
         int iconSize = getDrawableBounds(mView.getBackground()).width();
         float scale = launcher.getDragLayer().getLocationInDragLayer(mView, outPos);
@@ -91,9 +93,10 @@
             iconLeft = mView.getWidth() - iconSize - iconLeft;
         }
 
-        outPos[0] += Math.round(scale * iconLeft + (scale * iconSize - preview.getWidth()) / 2 +
-                mPositionShift.x);
-        outPos[1] += Math.round((scale * mView.getHeight() - preview.getHeight()) / 2
+        outPos[0] += Math.round(
+                scale * iconLeft + (scale * iconSize - preview.getIntrinsicWidth()) / 2
+                        + mPositionShift.x);
+        outPos[1] += Math.round((scale * mView.getHeight() - preview.getIntrinsicHeight()) / 2
                 + mPositionShift.y);
         float size = launcher.getDeviceProfile().iconSizePx;
         return scale * iconSize / size;
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index daec1d8..122573c 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -17,6 +17,8 @@
 
 import android.content.Context;
 
+import com.android.launcher3.DeviceProfile;
+
 /**
  * Interface representing a state of a StatefulActivity
  */
@@ -52,4 +54,11 @@
      * Returns if the state has the provided flag
      */
     boolean hasFlag(int flagMask);
+
+    /**
+     * For this state, whether tasks should layout as a grid rather than a list.
+     */
+    default boolean displayOverviewTasksAsGrid(DeviceProfile deviceProfile) {
+        return false;
+    }
 }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 2b51e97..51767e7 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -77,6 +77,15 @@
         return mCurrentStableState;
     }
 
+    @Override
+    public String toString() {
+        return " StateManager(mLastStableState:" + mLastStableState
+                + ", mCurrentStableState:" + mCurrentStableState
+                + ", mState:" + mState
+                + ", mRestState:" + mRestState
+                + ", isInTransition:" + (mConfig.currentAnimation != null) + ")";
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "StateManager:");
         writer.println(prefix + "\tmLastStableState:" + mLastStableState);
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 4fe631a..fc63af0 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -108,17 +108,18 @@
 
         // If the ImageView doesn't have a drawable yet, the widget preview hasn't been loaded and
         // we abort the drag.
-        if (image.getBitmap() == null) {
+        if (image.getDrawable() == null) {
             return false;
         }
 
         PendingItemDragHelper dragHelper = new PendingItemDragHelper(v);
-        dragHelper.setPreview(v.getPreview());
+        dragHelper.setRemoteViewsPreview(v.getPreview());
+        dragHelper.setAppWidgetHostViewPreview(v.getAppWidgetHostViewPreview());
 
         int[] loc = new int[2];
         getPopupContainer().getLocationInDragLayer(image, loc);
 
-        dragHelper.startDrag(image.getBitmapBounds(), image.getBitmap().getWidth(),
+        dragHelper.startDrag(image.getBitmapBounds(), image.getDrawable().getIntrinsicWidth(),
                 image.getWidth(), new Point(loc[0], loc[1]), this, new DragOptions());
         close(true);
         return true;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index ce97d2e..8689fbf 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.Utilities.ATLEAST_S;
+
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -33,6 +35,8 @@
     public int spanY;
     public int minSpanX;
     public int minSpanY;
+    public int maxSpanX;
+    public int maxSpanY;
 
     public static LauncherAppWidgetProviderInfo fromProviderInfo(Context context,
             AppWidgetProviderInfo info) {
@@ -78,15 +82,40 @@
                 || !idp.portraitProfile.shouldInsetWidgets()) {
             AppWidgetHostView.getDefaultPaddingForWidget(context, provider, widgetPadding);
         }
-        spanX = Math.max(1, (int) Math.ceil(
-                        (minWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
-        spanY = Math.max(1, (int) Math.ceil(
-                (minHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
 
-        minSpanX = Math.max(1, (int) Math.ceil(
-                (minResizeWidth + widgetPadding.left + widgetPadding.right) / smallestCellWidth));
-        minSpanY = Math.max(1, (int) Math.ceil(
-                (minResizeHeight + widgetPadding.top + widgetPadding.bottom) / smallestCellHeight));
+        minSpanX = getSpanX(widgetPadding, minResizeWidth, smallestCellWidth);
+        minSpanY = getSpanY(widgetPadding, minResizeHeight, smallestCellHeight);
+
+        // Use maxResizeWidth/Height if they are defined and we're on S or above.
+        maxSpanX =
+                (ATLEAST_S && maxResizeWidth > 0)
+                        ? getSpanX(widgetPadding, maxResizeWidth, smallestCellWidth)
+                        : idp.numColumns;
+        maxSpanY =
+                (ATLEAST_S && maxResizeHeight > 0)
+                        ? getSpanY(widgetPadding, maxResizeHeight, smallestCellHeight)
+                        : idp.numRows;
+
+        // Use targetCellWidth/Height if it is within the min/max ranges and we're on S or above.
+        // Otherwise, fall back to minWidth/Height.
+        if (ATLEAST_S && targetCellWidth >= minSpanX && targetCellWidth <= maxSpanX
+                && targetCellHeight >= minSpanY && targetCellHeight <= maxSpanY) {
+            spanX = targetCellWidth;
+            spanY = targetCellHeight;
+        } else {
+            spanX = getSpanX(widgetPadding, minWidth, smallestCellWidth);
+            spanY = getSpanY(widgetPadding, minHeight, smallestCellHeight);
+        }
+    }
+
+    private int getSpanX(Rect widgetPadding, int widgetWidth, float cellWidth) {
+        return Math.max(1, (int) Math.ceil(
+                (widgetWidth + widgetPadding.left + widgetPadding.right) / cellWidth));
+    }
+
+    private int getSpanY(Rect widgetPadding, int widgetHeight, float cellHeight) {
+        return Math.max(1, (int) Math.ceil(
+                (widgetHeight + widgetPadding.top + widgetPadding.bottom) / cellHeight));
     }
 
     public String getLabel(PackageManager packageManager) {
@@ -124,4 +153,4 @@
     public Drawable getFullResIcon(IconCache cache) {
         return cache.getFullResIcon(provider.getPackageName(), icon);
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index 6e83836..8961f36 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -29,10 +29,12 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DragSource;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -49,15 +51,26 @@
     private final PendingAddItemInfo mAddInfo;
     private int[] mEstimatedCellSize;
 
-    @Nullable private RemoteViews mPreview;
+    @Nullable private RemoteViews mRemoteViewsPreview;
+    @Nullable private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
 
     public PendingItemDragHelper(View view) {
         super(view);
         mAddInfo = (PendingAddItemInfo) view.getTag();
     }
 
-    public void setPreview(@Nullable RemoteViews preview) {
-        mPreview = preview;
+    /**
+     * Sets a {@link RemoteViews} which shows an app widget preview provided by app developers in
+     * the pin widget flow.
+     */
+    public void setRemoteViewsPreview(@Nullable RemoteViews remoteViewsPreview) {
+        mRemoteViewsPreview = remoteViewsPreview;
+    }
+
+    /** Sets a {@link LauncherAppWidgetHostView} which shows a preview layout of an app widget. */
+    public void setAppWidgetHostViewPreview(
+            @Nullable LauncherAppWidgetHostView appWidgetHostViewPreview) {
+        mAppWidgetHostViewPreview = appWidgetHostViewPreview;
     }
 
     /**
@@ -74,7 +87,7 @@
         final Launcher launcher = Launcher.getLauncher(mView.getContext());
         LauncherAppState app = LauncherAppState.getInstance(launcher);
 
-        Bitmap preview = null;
+        Drawable preview = null;
         final float scale;
         final Point dragOffset;
         final Rect dragRegion;
@@ -90,13 +103,19 @@
 
             int[] previewSizeBeforeScale = new int[1];
 
-            if (mPreview != null) {
-                preview = WidgetCell.generateFromRemoteViews(launcher, mPreview,
-                        createWidgetInfo.info, maxWidth, previewSizeBeforeScale);
+            if (mRemoteViewsPreview != null) {
+                preview = new FastBitmapDrawable(
+                        WidgetCell.generateFromRemoteViews(launcher, mRemoteViewsPreview,
+                                createWidgetInfo.info, maxWidth, previewSizeBeforeScale));
+            }
+            if (mAppWidgetHostViewPreview != null) {
+                preview = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
             }
             if (preview == null) {
-                preview = app.getWidgetCache().generateWidgetPreview(launcher,
-                        createWidgetInfo.info, maxWidth, null, previewSizeBeforeScale).first;
+                preview = new FastBitmapDrawable(
+                        app.getWidgetCache().generateWidgetPreview(launcher,
+                                createWidgetInfo.info, maxWidth, null,
+                                previewSizeBeforeScale).first);
             }
 
             if (previewSizeBeforeScale[0] < previewBitmapWidth) {
@@ -109,7 +128,7 @@
                 previewBounds.left += padding;
                 previewBounds.right -= padding;
             }
-            scale = previewBounds.width() / (float) preview.getWidth();
+            scale = previewBounds.width() / (float) preview.getIntrinsicWidth();
             launcher.getDragController().addDragListener(new WidgetHostViewLoader(launcher, mView));
 
             dragOffset = null;
@@ -119,9 +138,10 @@
             PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
             Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
             LauncherIcons li = LauncherIcons.obtain(launcher);
-            preview = li.createScaledBitmapWithoutShadow(icon, 0);
+            preview = new FastBitmapDrawable(
+                    li.createScaledBitmapWithoutShadow(icon, 0));
             li.recycle();
-            scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
+            scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getIntrinsicWidth();
 
             dragOffset = new Point(previewPadding / 2, previewPadding / 2);
 
@@ -149,9 +169,9 @@
         launcher.getWorkspace().prepareDragWithProvider(this);
 
         int dragLayerX = screenPos.x + previewBounds.left
-                + (int) ((scale * preview.getWidth() - preview.getWidth()) / 2);
+                + (int) ((scale * preview.getIntrinsicWidth() - preview.getIntrinsicWidth()) / 2);
         int dragLayerY = screenPos.y + previewBounds.top
-                + (int) ((scale * preview.getHeight() - preview.getHeight()) / 2);
+                + (int) ((scale * preview.getIntrinsicHeight() - preview.getIntrinsicHeight()) / 2);
 
         // Start the drag
         launcher.getDragController().startDrag(preview, draggableView, dragLayerX, dragLayerY,
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 229df50..5328041 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,10 +18,10 @@
 
 import static com.android.launcher3.Utilities.ATLEAST_S;
 
-import android.appwidget.AppWidgetHostView;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
 import android.os.CancellationSignal;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -35,11 +35,15 @@
 import android.widget.RemoteViews;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
 import com.android.launcher3.WidgetPreviewLoader;
+import com.android.launcher3.dragndrop.AppWidgetHostViewDrawable;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.model.WidgetItem;
@@ -84,14 +88,14 @@
     private boolean mAnimatePreview = true;
 
     private boolean mApplyBitmapDeferred = false;
-    private Bitmap mDeferredBitmap;
+    private Drawable mDeferredDrawable;
 
     protected final BaseActivity mActivity;
     protected final DeviceProfile mDeviceProfile;
     private final CheckLongPressHelper mLongPressHelper;
 
     private RemoteViews mPreview;
-    private AppWidgetHostView mPreviewAppWidgetHostView;
+    private LauncherAppWidgetHostView mAppWidgetHostViewPreview;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -147,7 +151,7 @@
             Log.d(TAG, "reset called on:" + mWidgetName.getText());
         }
         mWidgetImage.animate().cancel();
-        mWidgetImage.setBitmap(null, null);
+        mWidgetImage.setDrawable(null, null);
         mWidgetName.setText(null);
         mWidgetDims.setText(null);
         mWidgetDescription.setText(null);
@@ -159,7 +163,7 @@
             mActiveRequest = null;
         }
         mPreview = null;
-        mPreviewAppWidgetHostView = null;
+        mAppWidgetHostViewPreview = null;
     }
 
     public void applyFromCellItem(WidgetItem item, WidgetPreviewLoader loader) {
@@ -194,7 +198,7 @@
                 && mPreview == null
                 && item.widgetInfo != null
                 && item.widgetInfo.previewLayout != Resources.ID_NULL) {
-            mPreviewAppWidgetHostView = new AppWidgetHostView(getContext());
+            mAppWidgetHostViewPreview = new LauncherAppWidgetHostView(getContext());
             LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
                     LauncherAppWidgetProviderInfo.fromProviderInfo(getContext(),
                             item.widgetInfo.clone());
@@ -202,11 +206,11 @@
             // rendering a preview layout for work profile apps yet. For non-work profile layout, a
             // proper solution is to use RemoteViews(PackageName, LayoutId).
             launcherAppWidgetProviderInfo.initialLayout = item.widgetInfo.previewLayout;
-            mPreviewAppWidgetHostView.setAppWidget(/* appWidgetId= */ -1,
+            mAppWidgetHostViewPreview.setAppWidget(/* appWidgetId= */ -1,
                     launcherAppWidgetProviderInfo);
-            mPreviewAppWidgetHostView.setPadding(/* left= */ 0, /* top= */0, /* right= */
+            mAppWidgetHostViewPreview.setPadding(/* left= */ 0, /* top= */0, /* right= */
                     0, /* bottom= */ 0);
-            mPreviewAppWidgetHostView.updateAppWidget(/* remoteViews= */ null);
+            mAppWidgetHostViewPreview.updateAppWidget(/* remoteViews= */ null);
         }
     }
 
@@ -214,6 +218,11 @@
         return mWidgetImage;
     }
 
+    @Nullable
+    public LauncherAppWidgetHostView getAppWidgetHostViewPreview() {
+        return mAppWidgetHostViewPreview;
+    }
+
     /**
      * Sets if applying bitmap preview should be deferred. The UI will still load the bitmap, but
      * will not cause invalidate, so that when deferring is disabled later, all the bitmaps are
@@ -223,9 +232,9 @@
     public void setApplyBitmapDeferred(boolean isDeferred) {
         if (mApplyBitmapDeferred != isDeferred) {
             mApplyBitmapDeferred = isDeferred;
-            if (!mApplyBitmapDeferred && mDeferredBitmap != null) {
-                applyPreview(mDeferredBitmap);
-                mDeferredBitmap = null;
+            if (!mApplyBitmapDeferred && mDeferredDrawable != null) {
+                applyPreview(mDeferredDrawable);
+                mDeferredDrawable = null;
             }
         }
     }
@@ -235,17 +244,21 @@
     }
 
     public void applyPreview(Bitmap bitmap) {
+        applyPreview(new FastBitmapDrawable(bitmap));
+    }
+
+    private void applyPreview(Drawable drawable) {
         if (mApplyBitmapDeferred) {
-            mDeferredBitmap = bitmap;
+            mDeferredDrawable = drawable;
             return;
         }
-        if (bitmap != null) {
+        if (drawable != null) {
             LayoutParams layoutParams = (LayoutParams) mWidgetImage.getLayoutParams();
-            layoutParams.width = bitmap.getWidth();
-            layoutParams.height = bitmap.getHeight();
+            layoutParams.width = drawable.getIntrinsicWidth();
+            layoutParams.height = drawable.getIntrinsicHeight();
             mWidgetImage.setLayoutParams(layoutParams);
 
-            mWidgetImage.setBitmap(bitmap, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
+            mWidgetImage.setDrawable(drawable, mWidgetPreviewLoader.getBadgeForUser(mItem.user,
                     BaseIconFactory.getBadgeSizeForIconSize(mDeviceProfile.allAppsIconSizePx)));
             if (mAnimatePreview) {
                 mWidgetImage.setAlpha(0f);
@@ -262,18 +275,26 @@
             Bitmap preview = generateFromRemoteViews(
                     mActivity, mPreview, mItem.widgetInfo, mPresetPreviewSize, new int[1]);
             if (preview != null) {
-                applyPreview(preview);
+                applyPreview(new FastBitmapDrawable(preview));
                 return;
             }
         }
 
-        if (mPreviewAppWidgetHostView != null) {
-            Bitmap preview = generateFromView(mActivity, mPreviewAppWidgetHostView,
-                    mItem.widgetInfo, mPreviewWidth, new int[1]);
-            if (preview != null) {
-                applyPreview(preview);
-                return;
-            }
+        if (mAppWidgetHostViewPreview != null) {
+            DeviceProfile dp = mActivity.getDeviceProfile();
+            int viewWidth = dp.cellWidthPx * mItem.spanX;
+            int viewHeight = dp.cellHeightPx * mItem.spanY;
+
+            mAppWidgetHostViewPreview.measure(
+                    MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY));
+
+            viewWidth = mAppWidgetHostViewPreview.getMeasuredWidth();
+            viewHeight = mAppWidgetHostViewPreview.getMeasuredHeight();
+            mAppWidgetHostViewPreview.layout(0, 0, viewWidth, viewHeight);
+            Drawable drawable = new AppWidgetHostViewDrawable(mAppWidgetHostViewPreview);
+            applyPreview(drawable);
+            return;
         }
         if (mActiveRequest != null) {
             return;
diff --git a/src/com/android/launcher3/widget/WidgetImageView.java b/src/com/android/launcher3/widget/WidgetImageView.java
index df2bcff..39d701c 100644
--- a/src/com/android/launcher3/widget/WidgetImageView.java
+++ b/src/com/android/launcher3/widget/WidgetImageView.java
@@ -17,9 +17,7 @@
 package com.android.launcher3.widget;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -35,11 +33,10 @@
  */
 public class WidgetImageView extends View {
 
-    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
     private final RectF mDstRectF = new RectF();
     private final int mBadgeMargin;
 
-    private Bitmap mBitmap;
+    private Drawable mDrawable;
     private Drawable mBadge;
 
     public WidgetImageView(Context context) {
@@ -57,21 +54,22 @@
                 .getDimensionPixelSize(R.dimen.profile_badge_margin);
     }
 
-    public void setBitmap(Bitmap bitmap, Drawable badge) {
-        mBitmap = bitmap;
+    public void setDrawable(Drawable drawable, Drawable badge) {
+        mDrawable = drawable;
         mBadge = badge;
         invalidate();
     }
 
-    public Bitmap getBitmap() {
-        return mBitmap;
+    public Drawable getDrawable() {
+        return mDrawable;
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mBitmap != null) {
+        if (mDrawable != null) {
             updateDstRectF();
-            canvas.drawBitmap(mBitmap, null, mDstRectF, mPaint);
+            mDrawable.setBounds(getBitmapBounds());
+            mDrawable.draw(canvas);
 
             // Only draw the badge if a preview was drawn.
             if (mBadge != null) {
@@ -91,11 +89,11 @@
     private void updateDstRectF() {
         float myWidth = getWidth();
         float myHeight = getHeight();
-        float bitmapWidth = mBitmap.getWidth();
+        float bitmapWidth = mDrawable.getIntrinsicWidth();
 
         final float scale = bitmapWidth > myWidth ? myWidth / bitmapWidth : 1;
         float scaledWidth = bitmapWidth * scale;
-        float scaledHeight = mBitmap.getHeight() * scale;
+        float scaledHeight = mDrawable.getIntrinsicHeight() * scale;
 
         mDstRectF.left = (myWidth - scaledWidth) / 2;
         mDstRectF.right = (myWidth + scaledWidth) / 2;
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 6b3c71a..92994be 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -439,7 +439,8 @@
     public int getHeaderViewHeight() {
         return measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mCollapseHandle)
                 + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mHeaderTitle)
-                + measureHeightWithVerticalMargins(mSearchAndRecommendationViewHolder.mSearchBar);
+                + measureHeightWithVerticalMargins(
+                (View) mSearchAndRecommendationViewHolder.mSearchBar);
     }
 
     /** private the height, in pixel, + the vertical margins of a given view. */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
index 9ab6424..12d3f11 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecyclerView.java
@@ -34,6 +34,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry;
 
 /**
  * The widgets recycler view.
@@ -219,7 +220,8 @@
         int totalItemsHeight = 0;
         for (int i = 0; i < untilIndex; i++) {
             WidgetsListBaseEntry entry = mAdapter.getItems().get(i);
-            if (entry instanceof WidgetsListHeaderEntry) {
+            if (entry instanceof WidgetsListHeaderEntry ||
+                    entry instanceof WidgetsListSearchHeaderEntry) {
                 totalItemsHeight += mEstimatedWidgetListHeaderHeight;
             } else if (entry instanceof WidgetsListContentEntry) {
                 totalItemsHeight += mLastVisibleWidgetContentTableHeight;
diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
new file mode 100644
index 0000000..d68e87e
--- /dev/null
+++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker.search;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.R;
+import com.android.launcher3.search.SearchAlgorithm;
+import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+
+import java.util.List;
+
+/**
+ * View for a search bar with an edit text with a cancel button.
+ */
+public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSearchBar {
+    private WidgetsSearchBarController mController;
+    private EditText mEditText;
+    private ImageButton mCancelButton;
+
+    public LauncherWidgetsSearchBar(Context context) {
+        this(context, null, 0);
+    }
+
+    public LauncherWidgetsSearchBar(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public LauncherWidgetsSearchBar(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    public void initialize(List<WidgetsListBaseEntry> allWidgets,
+            SearchModeListener searchModeListener) {
+        SearchAlgorithm<WidgetsListBaseEntry> algo =
+                new SimpleWidgetsSearchAlgorithm(new SimpleWidgetsSearchPipeline(allWidgets));
+        mController = new WidgetsSearchBarController(
+                algo, mEditText, mCancelButton, searchModeListener);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mEditText = findViewById(R.id.widgets_search_bar_edit_text);
+        mCancelButton = findViewById(R.id.widgets_search_cancel_button);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mController.onDestroy();
+    }
+}
diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
index d8e9733..af6dc48 100644
--- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
+++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java
@@ -16,64 +16,21 @@
 
 package com.android.launcher3.widget.picker.search;
 
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.EditText;
-import android.widget.ImageButton;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.search.SearchAlgorithm;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 
 import java.util.List;
 
 /**
- * View for a search bar with an edit text with a cancel button.
+ * Interface for a widgets picker search bar.
  */
-public class WidgetsSearchBar extends LinearLayout {
-    private WidgetsSearchBarController mController;
-    private EditText mEditText;
-    private ImageButton mCancelButton;
-
-    public WidgetsSearchBar(Context context) {
-        this(context, null, 0);
-    }
-
-    public WidgetsSearchBar(@NonNull Context context,
-            @Nullable AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public WidgetsSearchBar(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
+public interface WidgetsSearchBar {
     /**
      * Attaches a controller to the search bar which interacts with {@code searchModeListener}.
      */
-    public void initialize(List<WidgetsListBaseEntry> allWidgets,
-            SearchModeListener searchModeListener) {
-        SearchAlgorithm<WidgetsListBaseEntry> algo =
-                new SimpleWidgetsSearchAlgorithm(new SimpleWidgetsSearchPipeline(allWidgets));
-        mController = new WidgetsSearchBarController(
-                algo, mEditText, mCancelButton, searchModeListener);
-    }
+    void initialize(List<WidgetsListBaseEntry> allWidgets, SearchModeListener searchModeListener);
 
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mEditText = findViewById(R.id.widgets_search_bar_edit_text);
-        mCancelButton = findViewById(R.id.widgets_search_cancel_button);
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        mController.onDestroy();
-    }
+    /**
+     * Sets the vertical location, in pixels, of this search bar relative to its top position.
+     */
+    void setTranslationY(float translationY);
 }