Merge "Add null check between calling onMotionPauseDetected() and Changed()" into ub-launcher3-master
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 5491daa..6c88e55 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -144,7 +144,7 @@
                     LauncherActivityInterface.INSTANCE);
             tvs.setDp(mDeviceProfile);
 
-            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
+            int launcherRotation = DisplayController.getDefaultDisplay(mContext).getInfo().rotation;
             if (mAppRotation < 0) {
                 mAppRotation = launcherRotation;
             }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index cc109f6..a1988a1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -95,8 +95,10 @@
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
@@ -233,6 +235,10 @@
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
+    private static final long SWIPE_PIP_TO_HOME_DURATION = 425;
+    private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
+    protected boolean mIsSwipingPipToHome;
+
     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
@@ -618,13 +624,6 @@
 
         updateSysUiFlags(mCurrentShift.value);
         applyWindowTransform();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationTargets != null) {
-                LiveTileOverlay.INSTANCE.update(
-                        mTaskViewSimulator.getCurrentRect(),
-                        mTaskViewSimulator.getCurrentCornerRadius());
-            }
-        }
 
         updateLauncherTransitionProgress();
     }
@@ -800,6 +799,8 @@
     }
 
     private void onSettledOnEndTarget() {
+        // Fast-finish the attaching animation if it's still running.
+        maybeUpdateRecentsAttachedState(false);
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
         switch (endTarget) {
             case HOME:
@@ -1053,25 +1054,46 @@
 
         if (mGestureState.getEndTarget() == HOME) {
             mTaskViewSimulator.setDrawsBelowRecents(false);
-            HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
-            RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
-            windowAnim.addAnimatorListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (mRecentsAnimationController == null) {
-                        // If the recents animation is interrupted, we still end the running
-                        // animation (not canceled) so this is still called. In that case, we can
-                        // skip doing any future work here for the current gesture.
-                        return;
-                    }
-                    // Finalize the state and notify of the change
-                    mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
-                }
-            });
             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
-            windowAnim.start(mContext, velocityPxPerMs);
+            final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+                    ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+                    : null;
+            HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
+            mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
+                    && runningTaskTarget != null
+                    && runningTaskTarget.pictureInPictureParams != null
+                    && TaskInfoCompat.isAutoEnterPipEnabled(
+                            runningTaskTarget.pictureInPictureParams)
+                    && TaskInfoCompat.getPipSourceRectHint(
+                            runningTaskTarget.pictureInPictureParams) != null;
+            if (mIsSwipingPipToHome) {
+                mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
+                        homeAnimFactory, runningTaskTarget);
+                mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
+                mSwipePipToHomeAnimator.setInterpolator(interpolator);
+                mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
+                mSwipePipToHomeAnimator.start();
+                mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
+            } else {
+                mSwipePipToHomeAnimator = null;
+                RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+                windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+                    @Override
+                    public void onAnimationSuccess(Animator animator) {
+                        if (mRecentsAnimationController == null) {
+                            // If the recents animation is interrupted, we still end the running
+                            // animation (not canceled) so this is still called. In that case,
+                            // we can skip doing any future work here for the current gesture.
+                            return;
+                        }
+                        // Finalize the state and notify of the change
+                        mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+                    }
+                });
+                windowAnim.start(mContext, velocityPxPerMs);
+                mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+            }
             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
-            mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
             mLauncherTransitionController = null;
         } else {
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
@@ -1115,6 +1137,47 @@
         }
     }
 
+    private SwipePipToHomeAnimator getSwipePipToHomeAnimator(HomeAnimationFactory homeAnimFactory,
+            RemoteAnimationTargetCompat runningTaskTarget) {
+        // Directly animate the app to PiP (picture-in-picture) mode
+        final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
+        final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+        final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
+                .startSwipePipToHome(taskInfo.topActivity,
+                        TaskInfoCompat.getTopActivityInfo(taskInfo),
+                        runningTaskTarget.pictureInPictureParams,
+                        orientationState.getRecentsActivityRotation(),
+                        mDp.hotseatBarSizePx);
+        final SwipePipToHomeAnimator swipePipToHomeAnimator = new SwipePipToHomeAnimator(
+                runningTaskTarget.taskId,
+                taskInfo.topActivity,
+                runningTaskTarget.leash.getSurfaceControl(),
+                TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
+                TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
+                destinationBounds);
+        swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure Launcher ends in NORMAL state, we intentionally skip other callbacks
+                // since they are not relevant in this swipe-pip-to-home case.
+                homeAnimFactory.createActivityAnimationToHome().dispatchOnStart();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mRecentsAnimationController == null) {
+                    // If the recents animation is interrupted, we still end the running
+                    // animation (not canceled) so this is still called. In that case, we can
+                    // skip doing any future work here for the current gesture.
+                    return;
+                }
+                // Finalize the state and notify of the change
+                mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+            }
+        });
+        return swipePipToHomeAnimator;
+    }
+
     private void computeRecentsScrollIfInvisible() {
         if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
             // Views typically don't compute scroll when invisible as an optimization,
@@ -1372,6 +1435,7 @@
             // If there are no targets or the animation not started, then there is nothing to finish
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
+            maybeFinishSwipePipToHome();
             finishRecentsControllerToHome(
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
@@ -1379,6 +1443,22 @@
         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
     }
 
+    /**
+     * Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
+     * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
+     */
+    private void maybeFinishSwipePipToHome() {
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+            SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
+                    mSwipePipToHomeAnimator.getComponentName(),
+                    mSwipePipToHomeAnimator.getDestinationBounds());
+            mRecentsAnimationController.setFinishTaskBounds(
+                    mSwipePipToHomeAnimator.getTaskId(),
+                    mSwipePipToHomeAnimator.getDestinationBounds());
+            mIsSwipingPipToHome = false;
+        }
+    }
+
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
@@ -1616,6 +1696,11 @@
             }
             mTaskViewSimulator.apply(mTransformParams);
         }
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
+            LiveTileOverlay.INSTANCE.update(
+                    mTaskViewSimulator.getCurrentRect(),
+                    mTaskViewSimulator.getCurrentCornerRadius());
+        }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 4411455..842fb84 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
             final TaskView runningTaskView = mRecentsView.getRunningTaskView();
             final View workspaceView;
             if (runningTaskView != null
+                    && !mIsSwipingPipToHome
                     && runningTaskView.getTask().key.getComponent() != null) {
                 workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
                         runningTaskView.getTask().key.getComponent().getPackageName(),
@@ -140,5 +141,10 @@
             new StaggeredWorkspaceAnim(mActivity, velocity,
                     true /* animateOverviewScrim */).start();
         }
+
+        @Override
+        public boolean supportSwipePipToHome() {
+            return true;
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 62bbf4d..7de658c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -18,6 +18,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.graphics.Rect;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
@@ -140,6 +142,18 @@
     }
 
     /**
+     * Sets the final bounds on a Task. This is used by Launcher to notify the system that
+     * animating Activity to PiP has completed and the associated task surface should be updated
+     * accordingly. This should be called before `finish`
+     * @param taskId for which the leash should be updated
+     * @param destinationBounds bounds of the final PiP window
+     */
+    public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+        UI_HELPER_EXECUTOR.execute(
+                () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+    }
+
+    /**
      * Enables the input consumer to start intercepting touches in the app window.
      */
     public void enableInputConsumer() {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 7406dea..8f7ec3b 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -165,6 +165,14 @@
         public void update(RectF currentRect, float progress, float radius) { }
 
         public void onCancel() { }
+
+        /**
+         * @return {@code true} if this factory supports animating an Activity to PiP window on
+         * swiping up to home.
+         */
+        public boolean supportSwipePipToHome() {
+            return false;
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
new file mode 100644
index 0000000..87fee79
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
+
+/**
+ * An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
+ * swiping up (in gesture navigation mode). Note that this class is derived from
+ * {@link com.android.wm.shell.pip.PipAnimationController.PipTransitionAnimator}.
+ *
+ * TODO: consider sharing this class including the animator and leash operations between
+ * Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
+ * PiP window, which would ideally be on SysUI side as well.
+ */
+public class SwipePipToHomeAnimator extends ValueAnimator implements
+        ValueAnimator.AnimatorUpdateListener {
+    private final int mTaskId;
+    private final ComponentName mComponentName;
+    private final SurfaceControl mLeash;
+    private final Rect mStartBounds = new Rect();
+    private final Rect mDestinationBounds = new Rect();
+    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+
+    /** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
+    private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+    private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
+    private final Rect mSourceHintRectInsets = new Rect();
+    private final Rect mSourceInsets = new Rect();
+
+    /**
+     * Flag to avoid the double-end problem since the leash would have been released
+     * after the first end call and any further operations upon it would lead to NPE.
+     */
+    private boolean mHasAnimationEnded;
+
+    public SwipePipToHomeAnimator(int taskId,
+            @NonNull ComponentName componentName,
+            @NonNull SurfaceControl leash,
+            @NonNull Rect sourceRectHint,
+            @NonNull Rect startBounds,
+            @NonNull Rect destinationBounds) {
+        mTaskId = taskId;
+        mComponentName = componentName;
+        mLeash = leash;
+        mStartBounds.set(startBounds);
+        mDestinationBounds.set(destinationBounds);
+        mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
+
+        mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
+                sourceRectHint.top - startBounds.top,
+                startBounds.right - sourceRectHint.right,
+                startBounds.bottom - sourceRectHint.bottom);
+
+        addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                SwipePipToHomeAnimator.this.onAnimationEnd();
+            }
+        });
+        addUpdateListener(this);
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animator) {
+        if (mHasAnimationEnded) return;
+
+        final float fraction = animator.getAnimatedFraction();
+        final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds, mDestinationBounds);
+        final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
+                mSourceHintRectInsets);
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
+        mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
+        tx.apply();
+    }
+
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public Rect getDestinationBounds() {
+        return mDestinationBounds;
+    }
+
+    private void onAnimationEnd() {
+        if (mHasAnimationEnded) return;
+
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBounds);
+        tx.apply();
+        mHasAnimationEnded = true;
+    }
+}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 6245637..1015a32 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -705,8 +705,13 @@
 
         updateIcon(icon);
 
+        ItemInfo itemInfo = (ItemInfo) getTag();
+
         // If the current icon is a placeholder color, animate its update.
-        if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable) {
+        if (mIcon != null
+                && mIcon instanceof PlaceHolderIconDrawable
+                && (itemInfo == null
+                    || itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
             animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon);
         }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 98328cf..777ea3c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -139,6 +139,10 @@
 
     public static final int DEFAULT_PAGE = 0;
 
+    private static final int DEFAULT_SMARTSPACE_HEIGHT = 1;
+
+    private static final int EXPANDED_SMARTSPACE_HEIGHT = 2;
+
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -507,7 +511,10 @@
                     .inflate(R.layout.search_container_workspace, firstPage, false);
         }
 
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
+        int cellVSpan = FeatureFlags.EXPANDED_SMARTSPACE.get()
+                ? EXPANDED_SMARTSPACE_HEIGHT : DEFAULT_SMARTSPACE_HEIGHT;
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(),
+                cellVSpan);
         lp.canReorder = false;
         if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
@@ -2078,40 +2085,40 @@
         mLastReorderY = -1;
     }
 
-   /*
-    *
-    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
-    * coordinate space. The argument xy is modified with the return result.
-    */
-   private void mapPointFromSelfToChild(View v, float[] xy) {
-       xy[0] = xy[0] - v.getLeft();
-       xy[1] = xy[1] - v.getTop();
-   }
+    /*
+     *
+     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+     * coordinate space. The argument xy is modified with the return result.
+     */
+    private void mapPointFromSelfToChild(View v, float[] xy) {
+        xy[0] = xy[0] - v.getLeft();
+        xy[1] = xy[1] - v.getTop();
+    }
 
-   boolean isPointInSelfOverHotseat(int x, int y) {
-       mTempFXY[0] = x;
-       mTempFXY[1] = y;
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
-       View hotseat = mLauncher.getHotseat();
-       return mTempFXY[0] >= hotseat.getLeft() &&
-               mTempFXY[0] <= hotseat.getRight() &&
-               mTempFXY[1] >= hotseat.getTop() &&
-               mTempFXY[1] <= hotseat.getBottom();
-   }
+    boolean isPointInSelfOverHotseat(int x, int y) {
+        mTempFXY[0] = x;
+        mTempFXY[1] = y;
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
+        View hotseat = mLauncher.getHotseat();
+        return mTempFXY[0] >= hotseat.getLeft()
+                && mTempFXY[0] <= hotseat.getRight()
+                && mTempFXY[1] >= hotseat.getTop()
+                && mTempFXY[1] <= hotseat.getBottom();
+    }
 
     /**
      * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
      * @param layout either hotseat of a page in workspace
      * @param xy the point location in workspace co-ordinate space
      */
-   private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
-       if (mLauncher.isHotseatLayout(layout)) {
-           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
-           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
-       } else {
-           mapPointFromSelfToChild(layout, xy);
-       }
-   }
+    private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
+        if (mLauncher.isHotseatLayout(layout)) {
+            mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
+            mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
+        } else {
+            mapPointFromSelfToChild(layout, xy);
+        }
+    }
 
     private boolean isDragWidget(DragObject d) {
         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
@@ -2393,9 +2400,9 @@
             spanY = mDragInfo.spanY;
         }
 
-        final int container = mLauncher.isHotseatLayout(cellLayout) ?
-                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        final int container = mLauncher.isHotseatLayout(cellLayout)
+                ? LauncherSettings.Favorites.CONTAINER_HOTSEAT
+                : LauncherSettings.Favorites.CONTAINER_DESKTOP;
         final int screenId = getIdForScreen(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 72b6d94..1fa43d0 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -45,6 +46,8 @@
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
 public class AllAppsRecyclerView extends BaseRecyclerView {
+    private static final String TAG = "AllAppsContainerView";
+    private static final boolean DEBUG = true;
 
     private AlphabeticalAppsList mApps;
     private final int mNumAppsPerRow;
@@ -131,7 +134,9 @@
         if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.draw(c);
         }
-
+        if (DEBUG) {
+            Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
+        }
         super.onDraw(c);
     }
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 58b0a87..8e6c2a7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -182,9 +182,13 @@
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
     public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
-            "ENABLE_MINIMAL_DEVICE", true,
+            "ENABLE_MINIMAL_DEVICE", false,
             "Allow user to toggle minimal device mode in launcher.");
 
+    public static final BooleanFlag EXPANDED_SMARTSPACE = new DeviceFlag(
+            "EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
+              + "Any apps occupying the first row will be removed from workspace.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index a27ac23..532834e 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -445,7 +445,8 @@
             if (item.screenId == Workspace.FIRST_SCREEN_ID) {
                 // Mark the first row as occupied (if the feature is enabled)
                 // in order to account for the QSB.
-                screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
+                int spanY = FeatureFlags.EXPANDED_SMARTSPACE.get() ? 2 : 1;
+                screen.markCells(0, 0, countX + 1, spanY, FeatureFlags.QSB_ON_FIRST_SCREEN);
             }
             occupied.put(item.screenId, screen);
         }
diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java
index fe904ff..bdbe890 100644
--- a/src/com/android/launcher3/views/SearchResultIconRow.java
+++ b/src/com/android/launcher3/views/SearchResultIconRow.java
@@ -63,11 +63,11 @@
     public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
     public static final String REMOTE_ACTION_TOKEN = "action_token";
 
-    private final int mCustomIconResId;
     private final boolean mMatchesInset;
 
     private SearchTarget mSearchTarget;
 
+    @Nullable private Drawable mCustomIcon;
 
     public SearchResultIconRow(@NonNull Context context) {
         this(context, null, 0);
@@ -83,10 +83,15 @@
         super(context, attrs, defStyleAttr);
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.SearchResultIconRow, defStyleAttr, 0);
-        mCustomIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
         mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery,
                 false);
 
+        int customIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
+
+        if (customIconResId != 0) {
+            mCustomIcon = Launcher.getLauncher(context).getDrawable(customIconResId);
+        }
+
         a.recycle();
     }
 
@@ -172,13 +177,16 @@
     }
 
     private boolean loadIconFromResource() {
-        if (mCustomIconResId == 0) return false;
-        setIcon(Launcher.getLauncher(getContext()).getDrawable(mCustomIconResId));
+        if (mCustomIcon == null) return false;
+        setIcon(mCustomIcon);
         return true;
     }
 
     void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
-        MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon));
+        MAIN_EXECUTOR.post(() -> {
+            reapplyItemInfo(itemInfoWithIcon);
+            mCustomIcon = getIcon();
+        });
     }
 
     @Override
diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
index d11b1ef..e929d7f 100644
--- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java
+++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
@@ -90,6 +90,7 @@
             bitmap = Bitmap.createBitmap(bitmap, 0,
                     bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
                     bitmap.getWidth(), bitmap.getWidth());
+            setTag(itemInfo);
         } else {
             bitmap = (Bitmap) target.getExtras().getParcelable("bitmap");
             WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();