Merge "Changes COMPILE_SDK to S." 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/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index be57dec..c3f5c00 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -15,11 +15,15 @@
  */
 package com.android.launcher3.model;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.formatElapsedTime;
+
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
 
 import android.app.prediction.AppPredictionContext;
@@ -29,10 +33,12 @@
 import android.app.prediction.AppTargetEvent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
@@ -40,12 +46,16 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.quickstep.logging.StatsLogCompatManager;
 
@@ -61,6 +71,10 @@
 public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
 
     public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+
+    private static final boolean IS_DEBUG = false;
+    private static final String TAG = "QuickstepModelDelegate";
 
     private final PredictorState mAllAppsState =
             new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
@@ -81,6 +95,7 @@
     }
 
     @Override
+    @WorkerThread
     public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
         // TODO: Implement caching and preloading
         super.loadItems(ums, pinnedShortcuts);
@@ -106,6 +121,38 @@
     }
 
     @Override
+    @WorkerThread
+    public void modelLoadComplete() {
+        super.modelLoadComplete();
+
+        // Log snapshot of the model
+        SharedPreferences prefs = getDevicePrefs(mApp.getContext());
+        long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
+        // Log snapshot only if previous snapshot was older than a day
+        long now = System.currentTimeMillis();
+        if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
+            if (IS_DEBUG) {
+                String elapsedTime = formatElapsedTime((now - lastSnapshotTimeMillis) / 1000);
+                Log.d(TAG, String.format(
+                        "Skipped snapshot logging since previous snapshot was %s old.",
+                        elapsedTime));
+            }
+        } else {
+            IntSparseArrayMap<ItemInfo> itemsIdMap;
+            synchronized (mDataModel) {
+                itemsIdMap = mDataModel.itemsIdMap.clone();
+            }
+            InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+            for (ItemInfo info : itemsIdMap) {
+                FolderInfo parent = info.container > 0
+                        ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
+            }
+            prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
+        }
+    }
+
+    @Override
     public void validateData() {
         super.validateData();
         if (mAllAppsState.predictor != null) {
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/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 32268a4..80308a5 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,19 +15,25 @@
  */
 package com.android.quickstep;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.UserManager;
 import android.util.Log;
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
+import com.android.launcher3.util.Executors;
+import com.android.quickstep.logging.SettingsChangeLogger;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
+@TargetApi(Build.VERSION_CODES.R)
 public class QuickstepProcessInitializer extends MainProcessInitializer {
 
     private static final String TAG = "QuickstepProcessInitializer";
+    private static final int SETUP_DELAY_MILLIS = 5000;
 
     public QuickstepProcessInitializer(Context context) { }
 
@@ -51,5 +57,9 @@
         // Elevate GPU priority for Quickstep and Remote animations.
         ThreadedRendererCompat.setContextPriority(
                 ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+
+        // Initialize settings logger after a default timeout
+        Executors.MAIN_EXECUTOR.getHandler()
+                .postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS);
     }
 }
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/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 5520ef7..46c7768 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -41,6 +41,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
@@ -129,6 +130,21 @@
         return taskView;
     }
 
+    public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+            PendingAnimation out) {
+        boolean isRunningTask = v.isRunningTask();
+        TransformParams params = null;
+        TaskViewSimulator tsv = null;
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+            params = v.getRecentsView().getLiveTileParams();
+            tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
+        }
+        createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets,
+                depthController, out, params, tsv);
+    }
+
     /**
      * Creates an animation that controls the window of the opening targets for the recents launch
      * animation.
@@ -136,19 +152,25 @@
     public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
-            PendingAnimation out) {
+            PendingAnimation out, @Nullable TransformParams params,
+            @Nullable TaskViewSimulator tsv) {
         boolean isQuickSwitch = v.isEndQuickswitchCuj();
         v.setEndQuickswitchCuj(false);
 
-        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+        boolean inLiveTileMode =
+                ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets,
-                        ENABLE_QUICKSTEP_LIVE_TILE.get() ? MODE_CLOSING : MODE_OPENING);
-        targets.addReleaseCheck(applier);
+                        inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
 
-        TransformParams params = new TransformParams()
+        if (params == null) {
+            SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+            targets.addReleaseCheck(applier);
+
+            params = new TransformParams()
                     .setSyncTransactionApplier(applier)
                     .setTargetSet(targets);
+        }
 
         final RecentsView recentsView = v.getRecentsView();
         int taskIndex = recentsView.indexOfChild(v);
@@ -162,8 +184,9 @@
         int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
 
         TaskViewSimulator topMostSimulator = null;
-        if (targets.apps.length > 0) {
-            TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
+
+        if (tsv == null && targets.apps.length > 0) {
+            tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
             tsv.setDp(dp);
             tsv.setLayoutRotation(displayRotation, displayRotation);
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
@@ -171,19 +194,24 @@
             tsv.recentsViewScale.value = 1;
             tsv.setScroll(startScroll);
 
+            // Fade in the task during the initial 20% of the animation
+            out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+                    clampToProgress(LINEAR, 0, 0.2f));
+        }
+
+        if (tsv != null) {
             out.setFloat(tsv.fullScreenProgress,
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
             out.setFloat(tsv.recentsViewScale,
                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
-            out.addOnFrameCallback(() -> tsv.apply(params));
+            TaskViewSimulator finalTsv = tsv;
+            TransformParams finalParams = params;
+            out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
             topMostSimulator = tsv;
         }
 
-        // Fade in the task during the initial 20% of the animation
-        out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
-
         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
 
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
new file mode 100644
index 0000000..0bb0bbc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -0,0 +1,155 @@
+/*
+ * 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.logging;
+
+import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED;
+import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.TypedArray;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Utility class to log launcher settings changes
+ */
+public class SettingsChangeLogger implements
+        NavigationModeChangeListener, OnSharedPreferenceChangeListener {
+
+    private static final String TAG = "SettingsChangeLogger";
+    private static final String ROOT_TAG = "androidx.preference.PreferenceScreen";
+    private static final String BOOLEAN_PREF = "SwitchPreference";
+
+    private final Context mContext;
+    private final ArrayMap<String, LoggablePref> mLoggablePrefs;
+
+    private Mode mNavMode;
+    private boolean mNotificationDotsEnabled;
+
+    public SettingsChangeLogger(Context context) {
+        mContext = context;
+        mLoggablePrefs = loadPrefKeys(context);
+        mNavMode = SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+
+        Utilities.getPrefs(context).registerOnSharedPreferenceChangeListener(this);
+        getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+
+        SecureSettingsObserver dotsObserver =
+                newNotificationSettingsObserver(context, this::onNotificationDotsChanged);
+        mNotificationDotsEnabled = dotsObserver.getValue();
+        dispatchUserEvent();
+
+    }
+
+    private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
+        XmlPullParser parser = context.getResources().getXml(R.xml.launcher_preferences);
+        ArrayMap<String, LoggablePref> result = new ArrayMap<>();
+
+        try {
+            AutoInstallsLayout.beginDocument(parser, ROOT_TAG);
+            final int depth = parser.getDepth();
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG
+                    || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                if (BOOLEAN_PREF.equals(parser.getName())) {
+                    TypedArray a = context.obtainStyledAttributes(
+                            Xml.asAttributeSet(parser), R.styleable.LoggablePref);
+                    String key = a.getString(R.styleable.LoggablePref_android_key);
+                    LoggablePref pref = new LoggablePref();
+                    pref.defaultValue =
+                            a.getBoolean(R.styleable.LoggablePref_android_defaultValue, true);
+                    pref.eventIdOn = a.getInt(R.styleable.LoggablePref_logIdOn, 0);
+                    pref.eventIdOff = a.getInt(R.styleable.LoggablePref_logIdOff, 0);
+                    if (pref.eventIdOff > 0 && pref.eventIdOn > 0) {
+                        result.put(key, pref);
+                    }
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.e(TAG, "Error parsing preference xml", e);
+        }
+        return result;
+    }
+
+    private void onNotificationDotsChanged(boolean isDotsEnabled) {
+        mNotificationDotsEnabled = isDotsEnabled;
+        dispatchUserEvent();
+    }
+
+    @Override
+    public void onNavigationModeChanged(Mode newMode) {
+        mNavMode = newMode;
+        dispatchUserEvent();
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (LAST_PREDICTION_ENABLED_STATE.equals(key) || mLoggablePrefs.containsKey(key)) {
+            dispatchUserEvent();
+        }
+    }
+
+    private void dispatchUserEvent() {
+        StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
+                .withInstanceId(new InstanceIdSequence().newInstanceId());
+
+        logger.log(mNotificationDotsEnabled
+                ? LAUNCHER_NOTIFICATION_DOT_ENABLED
+                : LAUNCHER_NOTIFICATION_DOT_DISABLED);
+        logger.log(mNavMode.launcherEvent);
+        logger.log(getDevicePrefs(mContext).getBoolean(LAST_PREDICTION_ENABLED_STATE, true)
+                ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED
+                : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED);
+
+        SharedPreferences prefs = Utilities.getPrefs(mContext);
+        mLoggablePrefs.forEach((key, lp) -> logger.log(() ->
+                prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
+    }
+
+    private static class LoggablePref {
+        public boolean defaultValue;
+        public int eventIdOn;
+        public int eventIdOff;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 059d158..d949126 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,10 +16,6 @@
 
 package com.android.quickstep.logging;
 
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
-import static android.text.format.DateUtils.formatElapsedTime;
-
-import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@@ -28,8 +24,6 @@
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
 
-import static java.lang.System.currentTimeMillis;
-
 import android.content.Context;
 import android.util.Log;
 
@@ -44,22 +38,16 @@
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.InstanceId;
-import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.LogConfig;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -78,7 +66,6 @@
 
     private static final String TAG = "StatsLog";
     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
-    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
     // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
     // from nano to lite, bake constant to prevent robo test failure.
@@ -101,71 +88,10 @@
     }
 
     /**
-     * Logs impression of the current workspace with additional launcher events.
+     * Synchronously writes an itemInfo to stats log
      */
-    @Override
-    public void logSnapshot(List<EventEnum> extraEvents) {
-        LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                new SnapshotWorker(extraEvents));
-    }
-
-    private class SnapshotWorker extends BaseModelUpdateTask {
-        private final InstanceId mInstanceId;
-        private final List<EventEnum> mExtraEvents;
-
-        SnapshotWorker(List<EventEnum> extraEvents) {
-            mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
-                    .newInstanceId();
-            this.mExtraEvents = extraEvents;
-        }
-
-        @Override
-        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-            long lastSnapshotTimeMillis = getDevicePrefs(mContext)
-                    .getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
-            // Log snapshot only if previous snapshot was older than a day
-            if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
-                if (IS_VERBOSE) {
-                    String elapsedTime = formatElapsedTime(
-                            (currentTimeMillis() - lastSnapshotTimeMillis) / 1000);
-                    Log.d(TAG, String.format(
-                            "Skipped snapshot logging since previous snapshot was %s old.",
-                            elapsedTime));
-                }
-                return;
-            }
-
-            IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
-            ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
-            ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
-            for (ItemInfo info : workspaceItems) {
-                LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo, mInstanceId);
-            }
-            for (FolderInfo fInfo : folders) {
-                try {
-                    ArrayList<WorkspaceItemInfo> folderContents =
-                            (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get();
-                    for (ItemInfo info : folderContents) {
-                        LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
-                        writeSnapshot(atomInfo, mInstanceId);
-                    }
-                } catch (Exception e) {
-                }
-            }
-            for (ItemInfo info : appWidgets) {
-                LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo, mInstanceId);
-            }
-            mExtraEvents
-                    .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
-
-            getDevicePrefs(mContext).edit()
-                    .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
-        }
-    }
-
-    private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
+    @WorkerThread
+    public static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
         if (IS_VERBOSE) {
             Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
         }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index d0f6879..da5f59e 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -193,7 +193,10 @@
                 if (isFirstDetectedPause) {
                     mOnMotionPauseListener.onMotionPauseDetected();
                 }
-                mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+                // Null check again as onMotionPauseDetected() maybe have called clear().
+                if (mOnMotionPauseListener != null) {
+                    mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+                }
             }
         }
     }
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/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index c6c2d7e..f6eb0e2 100644
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -65,6 +65,10 @@
         invalidateSelf();
     }
 
+    public void update(float left, float top, float right, float bottom) {
+        mCurrentRect.set(left, top, right, bottom);
+    }
+
     public void setIcon(Drawable icon) {
         mIcon = icon;
     }
@@ -94,18 +98,16 @@
 
     @Override
     public void draw(Canvas canvas) {
-        if (mCurrentRect != null) {
-            canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
-            if (mIcon != null && mIconAnimationProgress > 0f) {
-                canvas.save();
-                float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
-                        1f).getInterpolation(mIconAnimationProgress);
-                canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
-                        mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
-                canvas.scale(scale, scale);
-                mIcon.draw(canvas);
-                canvas.restore();
-            }
+        canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+        if (mIcon != null && mIconAnimationProgress > 0f) {
+            canvas.save();
+            float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+                    1f).getInterpolation(mIconAnimationProgress);
+            canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+                    mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+            canvas.scale(scale, scale);
+            mIcon.draw(canvas);
+            canvas.restore();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2158e03..826555c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -230,6 +230,7 @@
                     view.setScaleX(scale);
                     view.setScaleY(scale);
                     view.mLastComputedTaskPushOutDistance = null;
+                    view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
                     view.updatePageOffsets();
                     view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
                 }
@@ -876,6 +877,10 @@
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
             mLiveTileTaskViewSimulator.setOffsetY(0);
+
+            // Reset the live tile rect
+            DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+            LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1986,6 +1991,7 @@
             TaskView task = getTaskViewAt(i);
             mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
         }
+        mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
     }
 
     /**
@@ -2264,6 +2270,10 @@
         return mLiveTileTaskViewSimulator;
     }
 
+    public TransformParams getLiveTileParams() {
+        return mLiveTileParams;
+    }
+
     // TODO: To be removed in a follow up CL
     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
             RecentsAnimationTargets recentsAnimationTargets) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 54a793c..5154018 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -39,6 +39,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -65,7 +66,6 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -75,6 +75,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -82,10 +83,12 @@
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
@@ -175,7 +178,7 @@
     private float mCurveScale;
     private float mFullscreenProgress;
     private final FullscreenDrawParams mCurrentFullscreenParams;
-    private final BaseDraggingActivity mActivity;
+    private final StatefulActivity mActivity;
 
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
@@ -212,18 +215,31 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mActivity = BaseDraggingActivity.fromContext(context);
+        mActivity = StatefulActivity.fromContext(context);
         setOnClickListener((view) -> {
             if (getTask() == null) {
                 return;
             }
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                if (isRunningTask()) {
-                    // TODO: Replace this animation with createRecentsWindowAnimator
-                    createLaunchAnimationForRunningTask().start();
-                } else {
-                    launchTask(true /* animate */);
-                }
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+                RecentsView recentsView = getRecentsView();
+                RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
+                recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
+
+                AnimatorSet anim = new AnimatorSet();
+                TaskViewUtils.composeRecentsLaunchAnimator(
+                        anim, this, targets.apps,
+                        targets.wallpapers, true /* launcherClosing */,
+                        mActivity.getStateManager(), recentsView,
+                        recentsView.getDepthController());
+                anim.addListener(new AnimatorListenerAdapter() {
+
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
+                        recentsView.finishRecentsAnimation(false, null);
+                    }
+                });
+                anim.start();
             } else {
                 launchTask(true /* animate */);
             }
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6b0f300..c3ad1eb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -174,6 +174,14 @@
         <attr name="canThumbDetach" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="LoggablePref">
+        <attr name="android:key" />
+        <attr name="android:defaultValue" />
+        <!-- Ground truth of this Pref integer can be found in StatsLogManager -->
+        <attr name="logIdOn" format="integer" />
+        <attr name="logIdOff" format="integer" />
+    </declare-styleable>
+
     <declare-styleable name="PreviewFragment">
         <attr name="android:name" />
         <attr name="android:id" />
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 7e72208..e4bea50 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -15,7 +15,8 @@
 -->
 
 <androidx.preference.PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
     <com.android.launcher3.settings.NotificationDotsPreference
         android:key="pref_icon_badging"
@@ -30,19 +31,31 @@
         </intent>
     </com.android.launcher3.settings.NotificationDotsPreference>
 
+    <!--
+      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613)
+      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614)
+    -->
     <SwitchPreference
         android:key="pref_add_icon_to_home"
         android:title="@string/auto_add_shortcuts_label"
         android:summary="@string/auto_add_shortcuts_description"
         android:defaultValue="true"
-        android:persistent="true" />
+        android:persistent="true"
+        launcher:logIdOn="613"
+        launcher:logIdOff="614" />
 
+    <!--
+      LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615)
+      LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616)
+    -->
     <SwitchPreference
         android:key="pref_allowRotation"
         android:title="@string/allow_rotation_title"
         android:summary="@string/allow_rotation_desc"
         android:defaultValue="@bool/allow_rotation"
-        android:persistent="true" />
+        android:persistent="true"
+        launcher:logIdOn="615"
+        launcher:logIdOff="616" />
 
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
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/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index fd4c30c..70d8476 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -79,9 +79,7 @@
         public DraggableView originalView = null;
 
         /** Used for matching DROP event with its corresponding DRAG event on the server side. */
-        public final InstanceId logInstanceId =
-                new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
-                    .newInstanceId();
+        public final InstanceId logInstanceId = new InstanceIdSequence().newInstanceId();
 
         public DragObject(Context context) {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 370bd6f..ff53b5f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -201,7 +201,7 @@
                 DisplayController.getDefaultDisplay(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
-        Info myInfo = new Info(context, display);
+        Info myInfo = new Info(display);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
                 myInfo, getPredefinedDeviceProfiles(context, gridName));
 
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/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2c5bf32..7e90769 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -29,8 +29,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import java.util.List;
-
 /**
  * Handles the user event logging in R+.
  *
@@ -463,10 +461,4 @@
                 context.getApplicationContext(), R.string.stats_log_manager_class);
         return mgr;
     }
-
-    /**
-     * Logs impression of the current workspace with additional launcher events.
-     */
-    public void logSnapshot(List<EventEnum> additionalEvents) {
-    }
 }
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/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d47fafd..b108788 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -289,6 +289,7 @@
             updateHandler.finish();
             logger.addSplit("finish icon update");
 
+            mModelDelegate.modelLoadComplete();
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 3ed8809..92bea5b 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -78,6 +78,12 @@
     public void workspaceLoadComplete() { }
 
     /**
+     * Called at the end of model load task
+     */
+    @WorkerThread
+    public void modelLoadComplete() { }
+
+    /**
      * Called when the delegate is no loner needed
      */
     @WorkerThread
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 355c949..3ab736a 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -31,6 +31,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Utilities;
+
 import java.util.ArrayList;
 
 /**
@@ -157,13 +159,13 @@
         private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
         private DisplayController.Info mInfo;
 
-        private DisplayHolder(Context displayContext) {
+        private DisplayHolder(Context displayContext, Display display) {
             mDisplayContext = displayContext;
             // Note that the Display object must be obtained from DisplayManager which is
             // associated to the display context, so the Display is isolated from Activity and
             // Application to provide the actual state of device that excludes the additional
             // adjustment and override.
-            mInfo = new DisplayController.Info(mDisplayContext);
+            mInfo = new DisplayController.Info(display);
             mId = mInfo.id;
         }
 
@@ -180,22 +182,31 @@
         }
 
         protected void handleOnChange() {
+            Display display = Utilities.ATLEAST_R
+                    ? mDisplayContext.getDisplay()
+                    : mDisplayContext
+                        .getSystemService(DisplayManager.class)
+                        .getDisplay(mId);
+            if (display == null) {
+                return;
+            }
+
             Info oldInfo = mInfo;
-            Info info = new Info(mDisplayContext);
+            Info newInfo = new Info(display);
 
             int change = 0;
-            if (info.hasDifferentSize(oldInfo)) {
+            if (newInfo.hasDifferentSize(oldInfo)) {
                 change |= CHANGE_SIZE;
             }
-            if (oldInfo.rotation != info.rotation) {
+            if (newInfo.rotation != oldInfo.rotation) {
                 change |= CHANGE_ROTATION;
             }
-            if (info.singleFrameMs != oldInfo.singleFrameMs) {
+            if (newInfo.singleFrameMs != oldInfo.singleFrameMs) {
                 change |= CHANGE_FRAME_DELAY;
             }
 
             if (change != 0) {
-                mInfo = info;
+                mInfo = newInfo;
                 final int flags = change;
                 MAIN_EXECUTOR.execute(() -> notifyChange(flags));
             }
@@ -216,7 +227,7 @@
             // Use application context to create display context so that it can have its own
             // Resources.
             Context displayContext = context.getApplicationContext().createDisplayContext(display);
-            return new DisplayHolder(displayContext);
+            return new DisplayHolder(displayContext, display);
         }
     }
 
@@ -244,12 +255,7 @@
             this.metrics = metrics;
         }
 
-        private Info(Context context) {
-            this(context, context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY));
-        }
-
-        public Info(Context context, Display display) {
+        public Info(Display display) {
             id = display.getDisplayId();
             rotation = display.getRotation();
 
@@ -262,7 +268,8 @@
             display.getRealSize(realSize);
             display.getCurrentSizeRange(smallestSize, largestSize);
 
-            metrics = context.getResources().getDisplayMetrics();
+            metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
         }
 
         private boolean hasDifferentSize(Info info) {
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();