Merge "Replace the system UI visibility with appearance (Launcher)"
diff --git a/gradle.properties b/gradle.properties
index 7a51375..7f4c609 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -10,4 +10,4 @@
 PROTOBUF_DEPENDENCY=com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-7
 
 BUILD_TOOLS_VERSION=28.0.3
-COMPILE_SDK=android-R
+COMPILE_SDK=android-S
diff --git a/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
new file mode 100644
index 0000000..656379f
--- /dev/null
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_90;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Context;
+
+import com.android.quickstep.FallbackActivityInterface;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
+
+/**
+ * Tests for {@link RecentsOrientedState}
+ */
+@RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
+public class RecentsOrientedStateTest {
+
+    private RecentsOrientedState mR1, mR2;
+
+    @Before
+    public void setup() {
+        Context context = RuntimeEnvironment.application;
+        mR1 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
+        mR2 = new RecentsOrientedState(context, FallbackActivityInterface.INSTANCE, i -> { });
+        assertEquals(mR1.getStateId(), mR2.getStateId());
+    }
+
+    @Test
+    public void stateId_changesWithFlags() {
+        mR1.setGestureActive(true);
+        mR2.setGestureActive(false);
+        assertNotEquals(mR1.getStateId(), mR2.getStateId());
+
+        mR2.setGestureActive(true);
+        assertEquals(mR1.getStateId(), mR2.getStateId());
+    }
+
+    @Test
+    public void stateId_changesWithRecentsRotation() {
+        mR1.setRecentsRotation(ROTATION_90);
+        mR2.setRecentsRotation(ROTATION_180);
+        assertNotEquals(mR1.getStateId(), mR2.getStateId());
+
+        mR2.setRecentsRotation(ROTATION_90);
+        assertEquals(mR1.getStateId(), mR2.getStateId());
+    }
+
+    @Test
+    public void stateId_changesWithDisplayRotation() {
+        mR1.update(ROTATION_0, ROTATION_90);
+        mR2.update(ROTATION_0, ROTATION_180);
+        assertNotEquals(mR1.getStateId(), mR2.getStateId());
+
+        mR2.update(ROTATION_90, ROTATION_90);
+        assertNotEquals(mR1.getStateId(), mR2.getStateId());
+
+        mR2.update(ROTATION_90, ROTATION_0);
+        assertNotEquals(mR1.getStateId(), mR2.getStateId());
+
+        mR2.update(ROTATION_0, ROTATION_90);
+        assertEquals(mR1.getStateId(), mR2.getStateId());
+    }
+}
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..688f323 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -144,11 +144,11 @@
                     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;
             }
-            tvs.setLayoutRotation(launcherRotation, mAppRotation);
+            tvs.getOrientationState().update(launcherRotation, mAppRotation);
             if (mAppInsets == null) {
                 mAppInsets = new Rect(mLauncherInsets);
             }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 44d43c6..c724318 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -19,11 +19,10 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
@@ -48,12 +47,14 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.stream.Stream;
@@ -123,7 +124,8 @@
     @Override
     protected void onUiChangedWhileSleeping() {
         // Remove the snapshot because the content view may have obvious changes.
-        ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this);
+        UI_HELPER_EXECUTOR.execute(
+                () -> ActivityManagerWrapper.getInstance().invalidateHomeTaskSnapshot(this));
     }
 
     @Override
@@ -203,8 +205,7 @@
     @Override
     protected void closeOpenViews(boolean animate) {
         super.closeOpenViews(animate);
-        ActivityManagerWrapper.getInstance()
-                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
     }
 
     @Override
@@ -325,6 +326,8 @@
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
-        InteractionJankMonitorWrapper.init(getWindow().getDecorView());
+        if (Utilities.ATLEAST_R) {
+            InteractionJankMonitorWrapper.init(getWindow().getDecorView());
+        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 5c8463c..034d51f 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.systemui.shared.recents.utilities.Utilities.postAtFrontOfQueueAsynchronously;
 
 import android.animation.Animator;
@@ -62,7 +63,7 @@
         Runnable r = () -> {
             finishExistingAnimation();
             mAnimationResult = new AnimationResult(() -> {
-                runnable.run();
+                UI_HELPER_EXECUTOR.execute(runnable);
                 mAnimationResult = null;
             });
             onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index d3b7e22..4953138 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -251,7 +251,10 @@
      * Sets or updates the predicted items
      */
     public void setPredictedItems(FixedContainerItems items) {
-        if (!mLauncher.isWorkspaceLoading()
+        boolean shouldIgnoreVisibility = mLauncher.isWorkspaceLoading()
+                || mPredictedItems.equals(items.items)
+                || mHotseat.getShortcutsAndWidgets().getChildCount() < mHotSeatItemsCount;
+        if (!shouldIgnoreVisibility
                 && mHotseat.isShown()
                 && mHotseat.getWindowVisibility() == View.VISIBLE) {
             mHotseat.setOnVisibilityAggregatedCallback((isVisible) -> {
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/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a284f5d..950598c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -74,10 +74,10 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -175,8 +175,7 @@
 
     @Override
     protected void showAllAppsFromIntent(boolean alreadyOnHome) {
-        ActivityManagerWrapper.getInstance().closeSystemWindows(
-            CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
         super.showAllAppsFromIntent(alreadyOnHome);
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d210bc6..f73e2f2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -24,7 +26,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
-import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -49,11 +50,11 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.AssistantUtilities;
 import com.android.quickstep.util.OverviewToHomeAnim;
 import com.android.quickstep.views.RecentsView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 /**
  * Handles swiping up on the nav bar to go home from launcher, e.g. overview or all apps.
@@ -186,8 +187,8 @@
         if (topView != null) {
             topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
         }
-        mCurrentAnimation = builder.createPlaybackController()
-                .setOnCancelRunnable(this::clearState);
+        mCurrentAnimation = builder.createPlaybackController();
+        mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState));
     }
 
     private void clearState() {
@@ -233,8 +234,7 @@
                 AbstractFloatingView.closeAllOpenViews(mLauncher);
                 // TODO: add to WW log
             }
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         } else {
             // Quickly return to the state we came from (we didn't move far).
             ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 2a3bdbf..702c519 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -191,15 +192,17 @@
             return;
         }
         mNormalToHintOverviewScrimAnimator = null;
-        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+        mCurrentAnimation.getTarget().addListener(newCancelListener(() ->
             mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
                 mOverviewResistYAnim = AnimatorControllerWithResistance
                         .createRecentsResistanceFromOverviewAnim(mLauncher, null)
                         .createPlaybackController();
                 mReachedOverview = true;
                 maybeSwipeInteractionToOverviewComplete();
-            });
-        });
+            })));
+
+        mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+        mCurrentAnimation.dispatchOnCancel();
         mStartedOverview = true;
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index f378848..df433f8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
@@ -44,6 +45,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
@@ -92,6 +94,8 @@
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
     private final LauncherRecentsView mRecentsView;
+    protected final AnimatorListener mClearStateOnCancelListener =
+            newCancelListener(this::clearState);
 
     private boolean mNoIntercept;
     private LauncherState mStartState;
@@ -204,8 +208,8 @@
         config.duration = (long) (Math.max(mXRange, mYRange) * 2);
         config.animFlags = config.animFlags | SKIP_OVERVIEW;
         mNonOverviewAnim = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(toState, config)
-                .setOnCancelRunnable(this::clearState);
+                .createAnimationToNewWorkspace(toState, config);
+        mNonOverviewAnim.getTarget().addListener(mClearStateOnCancelListener);
     }
 
     private void setupOverviewAnimators() {
@@ -379,7 +383,8 @@
             if (canceled) {
                 // Let the state manager know that the animation didn't go to the target state,
                 // but don't clean up yet (we already clean up when the animation completes).
-                mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
+                mNonOverviewAnim.getTarget().removeListener(mClearStateOnCancelListener);
+                mNonOverviewAnim.dispatchOnCancel();
             }
             float startProgress = mNonOverviewAnim.getProgressFraction();
             float endProgress = canceled ? 0 : 1;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 037d988..3c9b808 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -245,29 +245,29 @@
         config.animFlags = animFlags;
         config.duration = maxAccuracy;
 
-        cancelPendingAnim();
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+            mCurrentAnimation.dispatchOnCancel();
+        }
 
+        mGoingBetweenStates = true;
         if (mFromState == OVERVIEW && mToState == NORMAL
                 && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) {
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
-            mPendingAnimation = mOverviewPortraitStateTouchHelper
-                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR);
-            Runnable onCancelRunnable = () -> {
-                cancelPendingAnim();
-                clearState();
-            };
-            mCurrentAnimation = mPendingAnimation.createPlaybackController()
-                    .setOnCancelRunnable(onCancelRunnable);
+            mGoingBetweenStates = false;
+            mCurrentAnimation = mOverviewPortraitStateTouchHelper
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR)
+                    .createPlaybackController();
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             RecentsView recentsView = mLauncher.getOverviewPanel();
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
                     mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mToState, config)
-                    .setOnCancelRunnable(this::clearState);
+                    .createAnimationToNewWorkspace(mToState, config);
         }
+        mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener);
 
         if (totalShift == 0) {
             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
@@ -276,13 +276,6 @@
         return 1 / totalShift;
     }
 
-    private void cancelPendingAnim() {
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
-        }
-    }
-
     @Override
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
             LauncherState targetState, float velocity, boolean isFling) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index b8c2030..fc9e1bb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -45,9 +45,9 @@
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 /**
  * Handles quick switching to a recent task from the home screen.
@@ -92,8 +92,7 @@
     public void onDragStart(boolean start, float startDisplacement) {
         super.onDragStart(start, startDisplacement);
         mStartContainerType = LAUNCHER_STATE_BACKGROUND;
-        ActivityManagerWrapper.getInstance()
-                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
     }
 
     @Override
@@ -107,8 +106,8 @@
         setupInterpolators(config);
         config.duration = (long) (getShiftRange() * 2);
         mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, config)
-                .setOnCancelRunnable(this::clearState);
+                .createAnimationToNewWorkspace(mToState, config);
+        mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener);
         mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator ->
                 updateFullscreenProgress((Float) valueAnimator.getAnimatedValue()));
         return 1 / getShiftRange();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 186caf6..9729695 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
@@ -50,16 +51,12 @@
         extends AnimatorListenerAdapter implements TouchController,
         SingleAxisSwipeDetector.Listener {
 
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
     protected final T mActivity;
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
     private final boolean mIsRtl;
 
-    private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
 
@@ -200,10 +197,8 @@
         }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setPlayFraction(0);
-        }
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
+            mCurrentAnimation.getTarget().removeListener(this);
+            mCurrentAnimation.dispatchOnCancel();
         }
 
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
@@ -216,15 +211,16 @@
         // The interpolator controlling the most prominent visual movement. We use this to determine
         // whether we passed SUCCESS_TRANSITION_PROGRESS.
         final Interpolator currentInterpolator;
+        PendingAnimation pa;
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
-            mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+            pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
             currentInterpolator = Interpolators.ZOOM_IN;
-            mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+            pa = mRecentsView.createTaskLaunchAnimation(
                     mTaskBeingDragged, maxDuration, currentInterpolator);
 
             // Since the thumbnail is what is filling the screen, based the end displacement on it.
@@ -234,12 +230,8 @@
             mEndDisplacement = secondaryLayerDimension - mTempCords[1];
         }
         mEndDisplacement *= verticalFactor;
+        mCurrentAnimation = pa.createPlaybackController();
 
-        if (mCurrentAnimation != null) {
-            mCurrentAnimation.setOnCancelRunnable(null);
-        }
-        mCurrentAnimation = mPendingAnimation.createPlaybackController()
-                .setOnCancelRunnable(this::clearState);
         // Setting this interpolator doesn't affect the visual motion, but is used to determine
         // whether we successfully reached the target state in onDragEnd().
         mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
@@ -303,27 +295,15 @@
             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
         }
 
-        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+        mCurrentAnimation.setEndAction(this::clearState);
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity, mEndDisplacement, animationDuration);
     }
 
-    private void onCurrentAnimationEnd(boolean wasSuccess) {
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(wasSuccess);
-            mPendingAnimation = null;
-        }
-        clearState();
-    }
-
     private void clearState() {
         mDetector.finishedScrolling();
         mDetector.setDetectableScrollConditions(0, false);
         mTaskBeingDragged = null;
         mCurrentAnimation = null;
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
-        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 851a070..c59c045 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -41,10 +41,8 @@
 import static com.android.quickstep.GestureState.STATE_END_TARGET_SET;
 import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
-import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
-import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -96,6 +94,7 @@
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.RecentsOrientedState;
 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;
@@ -125,7 +124,7 @@
         RecentsAnimationCallbacks.RecentsAnimationListener {
     private static final String TAG = "AbsSwipeUpHandler";
 
-    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[16] : null;
+    private static final String[] STATE_NAMES = DEBUG_STATES ? new String[17] : null;
 
     protected final BaseActivityInterface<?, T> mActivityInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
@@ -187,6 +186,8 @@
             getFlagForIndex(14, "STATE_START_NEW_TASK");
     private static final int STATE_CURRENT_TASK_FINISHED =
             getFlagForIndex(15, "STATE_CURRENT_TASK_FINISHED");
+    private static final int STATE_FINISH_WITH_NO_END =
+            getFlagForIndex(16, "STATE_FINISH_WITH_NO_END");
 
     private static final int LAUNCHER_UI_STATES =
             STATE_LAUNCHER_PRESENT | STATE_LAUNCHER_DRAWN | STATE_LAUNCHER_STARTED;
@@ -308,6 +309,8 @@
                 this::invalidateHandlerWithLauncher);
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_RESUME_LAST_TASK,
                 this::notifyTransitionCancelled);
+        mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED | STATE_FINISH_WITH_NO_END,
+                this::notifyTransitionCancelled);
 
         if (!ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mStateCallback.addChangeListener(STATE_APP_CONTROLLER_RECEIVED | STATE_LAUNCHER_PRESENT
@@ -380,7 +383,7 @@
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return;
         }
-        mTaskViewSimulator.setRecentsRotation(mActivity.getDisplay().getRotation());
+        mTaskViewSimulator.setOrientationState(mRecentsView.getPagedViewOrientedState());
 
         // If we've already ended the gesture and are going home, don't prepare recents UI,
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
@@ -685,6 +688,7 @@
             dp.updateInsets(targets.homeContentInsets);
             dp.updateIsSeascape(mContext);
             initTransitionEndpoints(dp);
+            mTaskViewSimulator.getOrientationState().setMultiWindowMode(dp.isMultiWindowMode);
         }
 
         // Notify when the animation starts
@@ -796,6 +800,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:
@@ -1017,7 +1023,7 @@
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(long duration);
 
-    private TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
+    private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
         @Override
         public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
                 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
@@ -1057,8 +1063,10 @@
             mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
                     && runningTaskTarget != null
                     && runningTaskTarget.pictureInPictureParams != null
-                    && runningTaskTarget.pictureInPictureParams.isAutoEnterEnabled()
-                    && runningTaskTarget.pictureInPictureParams.getSourceRectHint() != null;
+                    && TaskInfoCompat.isAutoEnterPipEnabled(
+                            runningTaskTarget.pictureInPictureParams)
+                    && TaskInfoCompat.getPipSourceRectHint(
+                            runningTaskTarget.pictureInPictureParams) != null;
             if (mIsSwipingPipToHome) {
                 mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
                         homeAnimFactory, runningTaskTarget);
@@ -1136,7 +1144,8 @@
         final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
         final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
-                .startSwipePipToHome(taskInfo.topActivity, taskInfo.topActivityInfo,
+                .startSwipePipToHome(taskInfo.topActivity,
+                        TaskInfoCompat.getTopActivityInfo(taskInfo),
                         runningTaskTarget.pictureInPictureParams,
                         orientationState.getRecentsActivityRotation(),
                         mDp.hotseatBarSizePx);
@@ -1144,8 +1153,8 @@
                 runningTaskTarget.taskId,
                 taskInfo.topActivity,
                 runningTaskTarget.leash.getSurfaceControl(),
-                runningTaskTarget.pictureInPictureParams.getSourceRectHint(),
-                taskInfo.configuration.windowConfiguration.getBounds(),
+                TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
+                TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
                 destinationBounds);
         swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -1250,6 +1259,7 @@
         if (mGestureState.getEndTarget() != null && !mGestureState.isRunningAnimationToLauncher()) {
             cancelCurrentAnimation();
         } else {
+            mStateCallback.setStateOnUiThread(STATE_FINISH_WITH_NO_END);
             reset();
         }
     }
@@ -1453,13 +1463,33 @@
 
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
+    private final TaskStackChangeListener mLiveTileRestartListener = new TaskStackChangeListener() {
+        @Override
+        public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
+                boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
+            if (mRecentsAnimationTargets.hasTask(task.taskId)) {
+                launchOtherTaskInLiveTileMode(task.taskId, mRecentsAnimationTargets.apps);
+            }
+            ActivityManagerWrapper.getInstance().unregisterTaskStackListener(
+                    mLiveTileRestartListener);
+        }
+    };
+
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
         endLauncherTransitionController();
         mActivityInterface.onSwipeUpToRecentsComplete();
         mRecentsView.onSwipeUpAnimationSuccess();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             mTaskAnimationManager.setLaunchOtherTaskInLiveTileModeHandler(
-                    this::launchOtherTaskInLiveTileMode);
+                    appearedTaskTarget -> {
+                        RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
+                                mRecentsAnimationTargets.apps,
+                                mRecentsAnimationTargets.apps.length + 1);
+                        apps[apps.length - 1] = appearedTaskTarget;
+                        launchOtherTaskInLiveTileMode(appearedTaskTarget.taskId, apps);
+                    });
+            ActivityManagerWrapper.getInstance().registerTaskStackListener(
+                    mLiveTileRestartListener);
         }
 
         SystemUiProxy.INSTANCE.get(mContext).onOverviewShown(false, TAG);
@@ -1467,23 +1497,16 @@
         reset();
     }
 
-    private void launchOtherTaskInLiveTileMode(RemoteAnimationTargetCompat appearedTaskTarget) {
-        TaskView taskView = mRecentsView.getTaskView(appearedTaskTarget.taskId);
+    private void launchOtherTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps) {
+        TaskView taskView = mRecentsView.getTaskView(taskId);
         if (taskView == null) {
             return;
         }
 
-        RemoteAnimationTargetCompat[] apps = Arrays.copyOf(
-                mRecentsAnimationTargets.apps,
-                mRecentsAnimationTargets.apps.length + 1);
-        apps[apps.length - 1] = appearedTaskTarget;
-        boolean launcherClosing =
-                taskIsATargetWithMode(apps, mActivity.getTaskId(), MODE_CLOSING);
-
         AnimatorSet anim = new AnimatorSet();
         TaskViewUtils.composeRecentsLaunchAnimator(
                 anim, taskView, apps,
-                mRecentsAnimationTargets.wallpapers, launcherClosing,
+                mRecentsAnimationTargets.wallpapers, true /* launcherClosing */,
                 mActivity.getStateManager(), mRecentsView,
                 mActivityInterface.getDepthController());
         anim.addListener(new AnimatorListenerAdapter(){
@@ -1551,11 +1574,6 @@
         mGestureEndCallback = gestureEndCallback;
     }
 
-    @Override
-    public long getStartTouchTime() {
-        return mTouchTimeMs;
-    }
-
     protected void linkRecentsViewScroll() {
         SurfaceTransactionApplier.create(mRecentsView, applier -> {
             mTransformParams.setSyncTransactionApplier(applier);
diff --git a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index 55f5424..efd4530 100644
--- a/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -132,9 +132,8 @@
 
         TaskViewSimulator tsv = new TaskViewSimulator(mActivity, mRecentsView.getSizeStrategy());
         tsv.setDp(mActivity.getDeviceProfile());
+        tsv.setOrientationState(mRecentsView.getPagedViewOrientedState());
         tsv.setPreview(runningTaskTarget);
-        tsv.setLayoutRotation(mRecentsView.getPagedViewOrientedState().getTouchRotation(),
-                mRecentsView.getPagedViewOrientedState().getDisplayRotation());
 
         TransformParams params = new TransformParams()
                 .setTargetSet(targets)
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index f788996..8d67ee6 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -142,6 +142,9 @@
     private Set<Integer> mPreviouslyAppearedTaskIds = new HashSet<>();
     private int mLastStartedTaskId = -1;
 
+    /** The time when the swipe up gesture is triggered. */
+    private long mSwipeUpStartTimeMs;
+
     public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
         mHomeIntent = componentObserver.getHomeIntent();
         mOverviewIntent = componentObserver.getOverviewIntent();
@@ -343,6 +346,14 @@
         mStateCallback.setState(STATE_RECENTS_ANIMATION_ENDED);
     }
 
+    void setSwipeUpStartTimeMs(long uptimeMs) {
+        mSwipeUpStartTimeMs = uptimeMs;
+    }
+
+    long getSwipeUpStartTimeMs() {
+        return mSwipeUpStartTimeMs;
+    }
+
     public void dump(PrintWriter pw) {
         pw.println("GestureState:");
         pw.println("  gestureID=" + mGestureId);
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index a210f76..43581ca 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -65,16 +65,14 @@
             return;
         }
 
-        ActivityManagerWrapper.getInstance()
-                .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         MAIN_EXECUTOR.execute(new RecentsActivityCommand<>());
     }
 
     @BinderThread
     public void onOverviewShown(boolean triggeredFromAltTab) {
         if (triggeredFromAltTab) {
-            ActivityManagerWrapper.getInstance()
-                    .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+            TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
         }
         MAIN_EXECUTOR.execute(new ShowRecentsCommand(triggeredFromAltTab));
     }
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/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index f319b94..a21c714 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -160,12 +160,5 @@
          * Callback made when a task started from the recents is ready for an app transition.
          */
         default void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {}
-
-        /**
-         * The time in milliseconds of the touch event that starts the recents animation.
-         */
-        default long getStartTouchTime() {
-            return 0;
-        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index 718c5ba..da0a664 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -41,4 +41,13 @@
     public boolean hasTargets() {
         return unfilteredApps.length != 0;
     }
+
+    public boolean hasTask(int taskId) {
+        for (RemoteAnimationTargetCompat target : unfilteredApps) {
+            if (target.taskId == taskId) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 8f7ec3b..4bb1bb5 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -79,7 +79,7 @@
         mTaskViewSimulator = new TaskViewSimulator(context, gestureState.getActivityInterface());
         mTransformParams = transformParams;
 
-        mTaskViewSimulator.setLayoutRotation(
+        mTaskViewSimulator.getOrientationState().update(
                 mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
                 mDeviceState.getRotationTouchHelper().getDisplayRotation());
         mTaskViewSimulator.setDrawsBelowRecents(true);
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 797797f..6f2f86e 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -119,7 +119,7 @@
                 }
             }
         });
-        final long eventTime = listener.getStartTouchTime();
+        final long eventTime = gestureState.getSwipeUpStartTimeMs();
         mCallbacks.addListener(gestureState);
         mCallbacks.addListener(listener);
         UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 3a47024..6677724 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -31,6 +31,7 @@
 import android.view.View;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import com.android.launcher3.BaseActivity;
@@ -157,9 +158,8 @@
                 getActionsView().setCallbacks(new OverlayUICallbacks() {
                     @Override
                     public void onShare() {
-                        endLiveTileMode(isAllowedByPolicy);
                         if (isAllowedByPolicy) {
-                            mImageApi.startShareActivity();
+                            endLiveTileMode(mImageApi::startShareActivity);
                         } else {
                             showBlockedByPolicyMessage();
                         }
@@ -168,8 +168,7 @@
                     @SuppressLint("NewApi")
                     @Override
                     public void onScreenshot() {
-                        endLiveTileMode(isAllowedByPolicy);
-                        saveScreenshot(task);
+                        endLiveTileMode(() -> saveScreenshot(task));
                     }
                 });
             }
@@ -178,17 +177,15 @@
         /**
          * End rendering live tile in Overview.
          *
-         * @param showScreenshot if it's true, we take a screenshot and switch to it.
+         * @param callback callback to run, after switching to screenshot
          */
-        public void endLiveTileMode(boolean showScreenshot) {
+        public void endLiveTileMode(@NonNull Runnable callback) {
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                 RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
-                if (showScreenshot) {
-                    recentsView.switchToScreenshot(
-                            () -> recentsView.finishRecentsAnimation(true /* toRecents */, null));
-                } else {
-                    recentsView.finishRecentsAnimation(true /* toRecents */, null);
-                }
+                recentsView.switchToScreenshot(
+                        () -> recentsView.finishRecentsAnimation(true /* toRecents */, callback));
+            } else {
+                callback.run();
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/TaskUtils.java b/quickstep/src/com/android/quickstep/TaskUtils.java
index 04b488d..c9db153 100644
--- a/quickstep/src/com/android/quickstep/TaskUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -27,6 +29,7 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.List;
@@ -86,4 +89,12 @@
         }
         return false;
     }
+
+    /**
+     * Requests that the system close any open system windows (including other SystemUI).
+     */
+    public static void closeSystemWindowsAsync(String reason) {
+        UI_HELPER_EXECUTOR.execute(
+                () -> ActivityManagerWrapper.getInstance().closeSystemWindows(reason));
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 46c7768..2aed76a 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -179,16 +179,17 @@
 
         Context context = v.getContext();
         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
-        // RecentsView never updates the display rotation until swipe-up so the value may be stale.
-        // Use the display value instead.
-        int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
-
         TaskViewSimulator topMostSimulator = null;
 
         if (tsv == null && targets.apps.length > 0) {
             tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
             tsv.setDp(dp);
-            tsv.setLayoutRotation(displayRotation, displayRotation);
+
+            // RecentsView never updates the display rotation until swipe-up so the value may
+            // be stale. Use the display value instead.
+            int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
+            tsv.getOrientationState().update(displayRotation, displayRotation);
+
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
             tsv.fullScreenProgress.value = 0;
             tsv.recentsViewScale.value = 1;
@@ -297,7 +298,6 @@
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
                 depthController, pa);
-        anim.play(pa.buildAnim());
 
         Animator childStateAnimation = null;
         // Found a visible recents task that matches the opening app, lets launch the app from there
@@ -330,7 +330,11 @@
                 }
             };
         }
-        anim.play(launcherAnim);
+        pa.add(launcherAnim);
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
+            pa.addOnFrameCallback(recentsView::redrawLiveTile);
+        }
+        anim.play(pa.buildAnim());
 
         // Set the current animation first, before adding windowAnimEndListener. Setting current
         // animation adds some listeners which need to be called before windowAnimEndListener
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 1e4f297..8ce1f51 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -45,6 +45,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Looper;
+import android.os.SystemClock;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputEvent;
@@ -447,6 +448,7 @@
                 // onConsumerInactive and wipe the previous gesture state
                 GestureState prevGestureState = new GestureState(mGestureState);
                 GestureState newGestureState = createGestureState(mGestureState);
+                newGestureState.setSwipeUpStartTimeMs(SystemClock.uptimeMillis());
                 mConsumer.onConsumerAboutToBeSwitched();
                 mGestureState = newGestureState;
                 mConsumer = newConsumer(prevGestureState, mGestureState, event);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 82f489f..f2f9fb7 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -63,6 +63,7 @@
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
@@ -362,8 +363,7 @@
         mInputEventReceiver.setBatchingEnabled(true);
 
         mActivityInterface.closeOverlay();
-        ActivityManagerWrapper.getInstance().closeSystemWindows(
-                CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+        TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
 
         // Notify the handler that the gesture has actually started
         mInteractionHandler.onGestureStarted(isLikelyToStartNewTask);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index 9bfe84f..498e561 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -32,8 +32,8 @@
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
+import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.ActiveGestureLog;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 /**
@@ -91,8 +91,7 @@
             mTargetHandledTouch = true;
             if (!mStartingInActivityBounds) {
                 mActivityInterface.closeOverlay();
-                ActivityManagerWrapper.getInstance()
-                        .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
+                TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
                 ActiveGestureLog.INSTANCE.addLog("startQuickstep");
             }
             if (mInputMonitor != null) {
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/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index bb84380..e273aeb 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -72,7 +72,6 @@
 
     private static final String TAG = "RecentsOrientedState";
     private static final boolean DEBUG = false;
-    private static final String DELIMITER_DOT = "\\.";
 
     private ContentObserver mSystemAutoRotateObserver =
             newContentObserver(new Handler(), t -> updateAutoRotateSetting());
@@ -129,6 +128,9 @@
     private int mFlags;
     private int mPreviousRotation = ROTATION_0;
 
+    // Combined int which encodes the full state.
+    private int mStateId = 0;
+
     /**
      * @param rotationChangeListener Callback for receiving rotation events when rotation watcher
      *                              is enabled
@@ -169,7 +171,7 @@
      */
     public boolean setRecentsRotation(@SurfaceRotation int recentsRotation) {
         mRecentsRotation = recentsRotation;
-        return update(mTouchRotation, mDisplayRotation);
+        return updateHandler();
     }
 
     /**
@@ -183,8 +185,7 @@
      * Sets if the swipe up gesture is currently running or not
      */
     public boolean setGestureActive(boolean isGestureActive) {
-        setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
-        return update(mTouchRotation, mDisplayRotation);
+        return setFlag(FLAG_SWIPE_UP_NOT_RUNNING, !isGestureActive);
     }
 
     /**
@@ -201,14 +202,13 @@
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         mPreviousRotation = touchRotation;
+        return updateHandler();
+    }
 
-        PagedOrientationHandler oldHandler = mOrientationHandler;
+    private boolean updateHandler() {
         if (mRecentsActivityRotation == mTouchRotation
                 || (canRecentsActivityRotate() && (mFlags & FLAG_SWIPE_UP_NOT_RUNNING) != 0)) {
             mOrientationHandler = PagedOrientationHandler.PORTRAIT;
-            if (DEBUG) {
-                Log.d(TAG, "current RecentsOrientedState: " + this);
-            }
         } else if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = PagedOrientationHandler.LANDSCAPE;
         } else if (mTouchRotation == ROTATION_270) {
@@ -219,19 +219,26 @@
         if (DEBUG) {
             Log.d(TAG, "current RecentsOrientedState: " + this);
         }
-        return oldHandler != mOrientationHandler;
+
+        int oldStateId = mStateId;
+        // Each SurfaceRotation value takes two bits
+        mStateId = (((((mFlags << 2)
+                | mDisplayRotation) << 2)
+                | mTouchRotation) << 3)
+                | (mRecentsRotation < 0 ? 7 : mRecentsRotation);
+        return mStateId != oldStateId;
     }
 
     @SurfaceRotation
     private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
         if (isRecentsActivityRotationAllowed()) {
-            return mRecentsRotation < ROTATION_0 ? displayRotation : mRecentsRotation;
+            return mRecentsRotation < 0 ? displayRotation : mRecentsRotation;
         } else {
             return ROTATION_0;
         }
     }
 
-    private void setFlag(int mask, boolean enabled) {
+    private boolean setFlag(int mask, boolean enabled) {
         boolean wasRotationEnabled = !TestProtocol.sDisableSensorRotation
                 && (mFlags & VALUE_ROTATION_WATCHER_ENABLED) == VALUE_ROTATION_WATCHER_ENABLED
                 && !canRecentsActivityRotate();
@@ -253,6 +260,7 @@
                 }
             });
         }
+        return updateHandler();
     }
 
     @Override
@@ -327,6 +335,13 @@
         return mRecentsActivityRotation;
     }
 
+    /**
+     * Returns an id that can be used to tracking internal changes
+     */
+    public int getStateId() {
+        return mStateId;
+    }
+
     public boolean isMultipleOrientationSupportedByDevice() {
         return (mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 == MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE;
@@ -509,14 +524,15 @@
     public String toString() {
         boolean systemRotationOn = (mFlags & FLAG_SYSTEM_ROTATION_ALLOWED) != 0;
         return "["
-                + "this=" + extractObjectNameAndAddress(super.toString())
-                + " mOrientationHandler=" +
-                    extractObjectNameAndAddress(mOrientationHandler.toString())
+                + "this=" + nameAndAddress(this)
+                + " mOrientationHandler=" + nameAndAddress(mOrientationHandler)
                 + " mDisplayRotation=" + mDisplayRotation
                 + " mTouchRotation=" + mTouchRotation
                 + " mRecentsActivityRotation=" + mRecentsActivityRotation
+                + " mRecentsRotation=" + mRecentsRotation
                 + " isRecentsActivityRotationAllowed=" + isRecentsActivityRotationAllowed()
                 + " mSystemRotation=" + systemRotationOn
+                + " mStateId=" + mStateId
                 + " mFlags=" + mFlags
                 + "]";
     }
@@ -533,13 +549,7 @@
                 : idp.portraitProfile;
     }
 
-    /**
-     * String conversion for only the helpful parts of {@link Object#toString()} method
-     * @param stringToExtract "foo.bar.baz.MyObject@1234"
-     * @return "MyObject@1234"
-     */
-    private static String extractObjectNameAndAddress(String stringToExtract) {
-        int index = stringToExtract.lastIndexOf(DELIMITER_DOT);
-        return index >= 0 ? stringToExtract.substring(index) : "";
+    private static String nameAndAddress(Object obj) {
+        return obj.getClass().getSimpleName() + "@" + obj.hashCode();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 02b5dfe..87fee79 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -21,13 +21,13 @@
 import android.animation.RectEvaluator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
-import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.graphics.RectF;
 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
@@ -44,7 +44,7 @@
     private final SurfaceControl mLeash;
     private final Rect mStartBounds = new Rect();
     private final Rect mDestinationBounds = new Rect();
-    private final SurfaceTransactionHelper mSurfaceTransactionHelper;
+    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
 
     /** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
     private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
@@ -69,7 +69,7 @@
         mLeash = leash;
         mStartBounds.set(startBounds);
         mDestinationBounds.set(destinationBounds);
-        mSurfaceTransactionHelper = new SurfaceTransactionHelper();
+        mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
 
         mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
                 sourceRectHint.top - startBounds.top,
@@ -95,7 +95,8 @@
                 mSourceHintRectInsets);
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
         mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
-        tx.setCornerRadius(mLeash, 0).apply();
+        mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
+        tx.apply();
     }
 
     public int getTaskId() {
@@ -114,49 +115,8 @@
         if (mHasAnimationEnded) return;
 
         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
-        mSurfaceTransactionHelper.resetScale(tx, mLeash, mDestinationBounds);
-        mSurfaceTransactionHelper.crop(tx, mLeash, mDestinationBounds);
-        tx.setCornerRadius(mLeash, 0).apply();
+        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBounds);
+        tx.apply();
         mHasAnimationEnded = true;
     }
-
-    /**
-     * Slim version of {@link com.android.wm.shell.pip.PipSurfaceTransactionHelper}
-     */
-    private static final class SurfaceTransactionHelper {
-        private final Matrix mTmpTransform = new Matrix();
-        private final float[] mTmpFloat9 = new float[9];
-        private final RectF mTmpSourceRectF = new RectF();
-        private final Rect mTmpDestinationRect = new Rect();
-
-        private void scaleAndCrop(SurfaceControl.Transaction tx, SurfaceControl leash,
-                Rect sourceBounds, Rect destinationBounds, Rect insets) {
-            mTmpSourceRectF.set(sourceBounds);
-            mTmpDestinationRect.set(sourceBounds);
-            mTmpDestinationRect.inset(insets);
-            // Scale by the shortest edge and offset such that the top/left of the scaled inset
-            // source rect aligns with the top/left of the destination bounds
-            final float scale = sourceBounds.width() <= sourceBounds.height()
-                    ? (float) destinationBounds.width() / sourceBounds.width()
-                    : (float) destinationBounds.height() / sourceBounds.height();
-            final float left = destinationBounds.left - insets.left * scale;
-            final float top = destinationBounds.top - insets.top * scale;
-            mTmpTransform.setScale(scale, scale);
-            tx.setMatrix(leash, mTmpTransform, mTmpFloat9)
-                    .setWindowCrop(leash, mTmpDestinationRect)
-                    .setPosition(leash, left, top);
-        }
-
-        private void resetScale(SurfaceControl.Transaction tx, SurfaceControl leash,
-                Rect destinationBounds) {
-            tx.setMatrix(leash, Matrix.IDENTITY_MATRIX, mTmpFloat9)
-                    .setPosition(leash, destinationBounds.left, destinationBounds.top);
-        }
-
-        private void crop(SurfaceControl.Transaction tx, SurfaceControl leash,
-                Rect destinationBounds) {
-            tx.setWindowCrop(leash, destinationBounds.width(), destinationBounds.height())
-                    .setPosition(leash, destinationBounds.left, destinationBounds.top);
-        }
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 08fe48d..2f4bb8e 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -23,7 +23,6 @@
 
 import android.animation.TimeInterpolator;
 import android.content.Context;
-import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -31,6 +30,8 @@
 import android.graphics.RectF;
 import android.util.IntProperty;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
@@ -52,27 +53,28 @@
 
     public static final IntProperty<TaskViewSimulator> SCROLL =
             new IntProperty<TaskViewSimulator>("scroll") {
-        @Override
-        public void setValue(TaskViewSimulator simulator, int i) {
-            simulator.setScroll(i);
-        }
+                @Override
+                public void setValue(TaskViewSimulator simulator, int scroll) {
+                    simulator.setScroll(scroll);
+                }
 
-        @Override
-        public Integer get(TaskViewSimulator simulator) {
-            return simulator.mScrollState.scroll;
-        }
-    };
+                @Override
+                public Integer get(TaskViewSimulator simulator) {
+                    return simulator.mScrollState.scroll;
+                }
+            };
 
     private final Rect mTmpCropRect = new Rect();
     private final RectF mTempRectF = new RectF();
     private final float[] mTempPoint = new float[2];
 
-    private final RecentsOrientedState mOrientationState;
     private final Context mContext;
     private final BaseActivityInterface mSizeStrategy;
 
+    @NonNull
+    private RecentsOrientedState mOrientationState;
+
     private final Rect mTaskRect = new Rect();
-    private float mOffsetY;
     private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
@@ -89,6 +91,8 @@
     // TaskView properties
     private final FullscreenDrawParams mCurrentFullscreenParams;
     private float mCurveScale = 1;
+    public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
+    public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
 
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
@@ -99,6 +103,7 @@
     // Cached calculations
     private boolean mLayoutValid = false;
     private boolean mScrollValid = false;
+    private int mOrientationStateId;
 
     public TaskViewSimulator(Context context, BaseActivityInterface sizeStrategy) {
         mContext = context;
@@ -106,8 +111,8 @@
 
         mOrientationState = new RecentsOrientedState(context, sizeStrategy, i -> { });
         mOrientationState.setGestureActive(true);
-
         mCurrentFullscreenParams = new FullscreenDrawParams(context);
+        mOrientationStateId = mOrientationState.getStateId();
     }
 
     /**
@@ -115,23 +120,14 @@
      */
     public void setDp(DeviceProfile dp) {
         mDp = dp;
-        mOrientationState.setMultiWindowMode(mDp.isMultiWindowMode);
         mLayoutValid = false;
     }
 
     /**
-     * @see com.android.quickstep.views.RecentsView#setLayoutRotation(int, int)
+     * Sets the orientation state used for this animation
      */
-    public void setLayoutRotation(int touchRotation, int displayRotation) {
-        mOrientationState.update(touchRotation, displayRotation);
-        mLayoutValid = false;
-    }
-
-    /**
-     * @see com.android.quickstep.views.RecentsView#onConfigurationChanged(Configuration)
-     */
-    public void setRecentsRotation(int recentsRotation) {
-        mOrientationState.setRecentsRotation(recentsRotation);
+    public void setOrientationState(@NonNull RecentsOrientedState orientationState) {
+        mOrientationState = orientationState;
         mLayoutValid = false;
     }
 
@@ -178,10 +174,6 @@
         }
     }
 
-    public void setOffsetY(float offsetY) {
-        mOffsetY = offsetY;
-    }
-
     public void setDrawsBelowRecents(boolean drawsBelowRecents) {
         mDrawsBelowRecents = drawsBelowRecents;
     }
@@ -254,8 +246,9 @@
         if (mDp == null || mThumbnailPosition.isEmpty()) {
             return;
         }
-        if (!mLayoutValid) {
+        if (!mLayoutValid || mOrientationStateId != mOrientationState.getStateId()) {
             mLayoutValid = true;
+            mOrientationStateId = mOrientationState.getStateId();
 
             getFullScreenScale();
             mThumbnailData.rotation = mOrientationState.getDisplayRotation();
@@ -298,7 +291,11 @@
 
         // Apply TaskView matrix: scale, translate, scroll
         mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
-        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY);
+        mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+        mOrientationState.getOrientationHandler().set(mMatrix, MATRIX_POST_TRANSLATE,
+                taskPrimaryTranslation.value);
+        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
+                taskSecondaryTranslation.value);
         mOrientationState.getOrientationHandler().set(
                 mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
 
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 95bde80..6630aed 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -17,6 +17,8 @@
 package com.android.quickstep.views;
 
 import static android.provider.Settings.ACTION_APP_USAGE_SETTINGS;
+import static android.view.Gravity.BOTTOM;
+import static android.view.Gravity.CENTER_HORIZONTAL;
 
 import static com.android.launcher3.Utilities.prefixTextWithIcon;
 import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
@@ -27,6 +29,7 @@
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.AppUsageLimit;
+import android.graphics.Outline;
 import android.icu.text.MeasureFormat;
 import android.icu.text.MeasureFormat.FormatWidth;
 import android.icu.util.Measure;
@@ -35,6 +38,9 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import androidx.annotation.StringRes;
@@ -61,6 +67,10 @@
     private Task mTask;
     private boolean mHasLimit;
     private long mAppRemainingTimeMs;
+    private View mBanner;
+    private ViewOutlineProvider mOldBannerOutlineProvider;
+    private float mBannerOffsetPercentage;
+    private float mBannerAlpha = 1f;
 
     public DigitalWellBeingToast(BaseDraggingActivity activity, TaskView taskView) {
         mActivity = activity;
@@ -68,18 +78,10 @@
         mLauncherApps = activity.getSystemService(LauncherApps.class);
     }
 
-    private void setTaskFooter(View view) {
-        View oldFooter = mTaskView.setFooter(TaskView.INDEX_DIGITAL_WELLBEING_TOAST, view);
-        if (oldFooter != null) {
-            oldFooter.setOnClickListener(null);
-            mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, oldFooter);
-        }
-    }
-
     private void setNoLimit() {
         mHasLimit = false;
         mTaskView.setContentDescription(mTask.titleDescription);
-        setTaskFooter(null);
+        replaceBanner(null);
         mAppRemainingTimeMs = 0;
     }
 
@@ -90,7 +92,7 @@
                 mActivity, mTaskView);
         toast.setText(prefixTextWithIcon(mActivity, R.drawable.ic_hourglass_top, getText()));
         toast.setOnClickListener(this::openAppUsageSettings);
-        setTaskFooter(toast);
+        replaceBanner(toast);
 
         mTaskView.setContentDescription(
                 getContentDescriptionForTask(mTask, appUsageLimitTimeMs, appRemainingTimeMs));
@@ -233,4 +235,64 @@
                         getText(appRemainingTimeMs)) :
                 task.titleDescription;
     }
+
+    private void replaceBanner(View view) {
+        resetOldBanner();
+        setBanner(view);
+    }
+
+    private void resetOldBanner() {
+        if (mBanner != null) {
+            mBanner.setOutlineProvider(mOldBannerOutlineProvider);
+            mTaskView.removeView(mBanner);
+            mBanner.setOnClickListener(null);
+            mActivity.getViewCache().recycleView(R.layout.digital_wellbeing_toast, mBanner);
+        }
+    }
+
+    private void setBanner(View view) {
+        mBanner = view;
+        if (view != null) {
+            setupAndAddBanner();
+            setBannerOutline();
+        }
+    }
+
+    private void setupAndAddBanner() {
+        FrameLayout.LayoutParams layoutParams =
+                (FrameLayout.LayoutParams) mBanner.getLayoutParams();
+        layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
+        layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
+                mTaskView.getThumbnail().getLayoutParams()).bottomMargin;
+        mBanner.setTranslationY(mBannerOffsetPercentage * mBanner.getHeight());
+        mBanner.setAlpha(mBannerAlpha);
+        mTaskView.addView(mBanner);
+    }
+
+    private void setBannerOutline() {
+        mOldBannerOutlineProvider = mBanner.getOutlineProvider();
+        mBanner.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                mOldBannerOutlineProvider.getOutline(view, outline);
+                outline.offset(0, -Math.round(view.getTranslationY()));
+            }
+        });
+        mBanner.setClipToOutline(true);
+    }
+
+    void updateBannerOffset(float offsetPercentage) {
+        if (mBanner != null && mBannerOffsetPercentage != offsetPercentage) {
+            mBannerOffsetPercentage = offsetPercentage;
+            mBanner.setTranslationY(offsetPercentage * mBanner.getHeight());
+            mBanner.invalidateOutline();
+        }
+    }
+
+    void updateBannerAlpha(float alpha) {
+        if (mBanner != null && mBannerAlpha != alpha) {
+            mBannerAlpha = alpha;
+            mBanner.setAlpha(alpha);
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 826555c..f281296 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -39,7 +40,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
@@ -51,6 +51,7 @@
 import android.animation.LayoutTransition;
 import android.animation.LayoutTransition.TransitionListener;
 import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.app.ActivityManager.RunningTaskInfo;
@@ -88,6 +89,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
+import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.InvariantDeviceProfile;
@@ -98,8 +100,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PendingAnimation.EndState;
-import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -107,13 +107,13 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.OverScroller;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
+import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
@@ -123,7 +123,6 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.TaskUtils;
 import com.android.quickstep.ViewUtils;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
@@ -241,7 +240,7 @@
                 }
             };
 
-    protected RecentsOrientedState mOrientationState;
+    protected final RecentsOrientedState mOrientationState;
     protected final BaseActivityInterface mSizeStrategy;
     protected RecentsAnimationController mRecentsAnimationController;
     protected SurfaceTransactionApplier mSyncTransactionApplier;
@@ -410,17 +409,18 @@
     private int mTaskViewStartIndex = 0;
     private OverviewActionsView mActionsView;
 
-    private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
-            (inMultiWindowMode) -> {
-                if (mOrientationState != null) {
+    private MultiWindowModeChangedListener mMultiWindowModeChangedListener =
+            new MultiWindowModeChangedListener() {
+                @Override
+                public void onMultiWindowModeChanged(boolean inMultiWindowMode) {
                     mOrientationState.setMultiWindowMode(inMultiWindowMode);
                     setLayoutRotation(mOrientationState.getTouchRotation(),
                             mOrientationState.getDisplayRotation());
                     updateChildTaskOrientations();
-                }
-                if (!inMultiWindowMode && mOverviewStateEnabled) {
-                    // TODO: Re-enable layout transitions for addition of the unpinned task
-                    reloadIfNeeded();
+                    if (!inMultiWindowMode && mOverviewStateEnabled) {
+                        // TODO: Re-enable layout transitions for addition of the unpinned task
+                        reloadIfNeeded();
+                    }
                 }
             };
 
@@ -478,9 +478,7 @@
 
         mLiveTileTaskViewSimulator = new TaskViewSimulator(getContext(), getSizeStrategy());
         mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
-                getPagedViewOrientedState().getDisplayRotation());
-        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+        mLiveTileTaskViewSimulator.setOrientationState(mOrientationState);
         mLiveTileTaskViewSimulator.setDrawsBelowRecents(true);
     }
 
@@ -773,7 +771,7 @@
 
     protected void applyLoadPlan(ArrayList<Task> tasks) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks));
+            mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
             return;
         }
 
@@ -874,9 +872,10 @@
             // Since we reuse the same mLiveTileTaskViewSimulator in the RecentsView, we need
             // to reset the params after it settles in Overview from swipe up so that we don't
             // render with obsolete param values.
+            mLiveTileTaskViewSimulator.taskPrimaryTranslation.value = 0;
+            mLiveTileTaskViewSimulator.taskSecondaryTranslation.value = 0;
             mLiveTileTaskViewSimulator.fullScreenProgress.value = 0;
             mLiveTileTaskViewSimulator.recentsViewScale.value = 1;
-            mLiveTileTaskViewSimulator.setOffsetY(0);
 
             // Reset the live tile rect
             DeviceProfile deviceProfile = mActivity.getDeviceProfile();
@@ -1464,19 +1463,10 @@
                 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
     }
 
-    private void removeTask(TaskView taskView, int index, EndState endState) {
-        if (taskView.getTask() != null) {
-            ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
-            ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
-            mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
-                    .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
-        }
-    }
-
     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
             boolean shouldRemoveTask, long duration) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.createPlaybackController().dispatchOnCancel();
         }
         PendingAnimation anim = new PendingAnimation(duration);
 
@@ -1545,7 +1535,10 @@
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRunningTaskView() == taskView) {
             anim.addOnFrameCallback(() -> {
-                mLiveTileTaskViewSimulator.setOffsetY(taskView.getTranslationY());
+                mLiveTileTaskViewSimulator.taskSecondaryTranslation.value =
+                        mOrientationHandler.getSecondaryValue(
+                                taskView.getTranslationX(),
+                                taskView.getTranslationY());
                 redrawLiveTile();
             });
         }
@@ -1556,22 +1549,27 @@
         }
 
         mPendingAnimation = anim;
-        mPendingAnimation.addEndListener(new Consumer<EndState>() {
+        mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
-            public void accept(EndState endState) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                        taskView.isRunningTask() && endState.isSuccess) {
-                    finishRecentsAnimation(true /* toHome */, () -> onEnd(endState));
+            public void accept(Boolean success) {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask() && success) {
+                    finishRecentsAnimation(true /* toHome */, () -> onEnd(success));
                 } else {
-                    onEnd(endState);
+                    onEnd(success);
                 }
             }
 
             @SuppressWarnings("WrongCall")
-            private void onEnd(EndState endState) {
-                if (endState.isSuccess) {
+            private void onEnd(boolean success) {
+                if (success) {
                     if (shouldRemoveTask) {
-                        removeTask(taskView, draggedIndex, endState);
+                        if (taskView.getTask() != null) {
+                            UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+                                    .removeTask(taskView.getTask().key.id));
+                            mActivity.getStatsLogManager().logger()
+                                    .withItemInfo(taskView.getItemInfo())
+                                    .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
+                        }
                     }
 
                     int pageToSnapTo = mCurrentPage;
@@ -1610,10 +1608,11 @@
         }
 
         mPendingAnimation = anim;
-        mPendingAnimation.addEndListener((endState) -> {
-            if (endState.isSuccess) {
+        mPendingAnimation.addEndListener(isSuccess -> {
+            if (isSuccess) {
                 // Remove all the task views now
-                ActivityManagerWrapper.getInstance().removeAllRecentTasks();
+                UI_HELPER_EXECUTOR.execute(
+                        ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
                 removeTasksViewsAndClearAllButton();
                 startHome();
             }
@@ -1638,7 +1637,6 @@
     protected void runDismissAnimation(PendingAnimation pendingAnim) {
         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
         controller.dispatchOnStart();
-        controller.setEndAction(() -> pendingAnim.finish(true));
         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
         controller.start();
     }
@@ -1765,7 +1763,7 @@
         if (mOrientationState.setRecentsRotation(rotation)) {
             updateOrientationHandler();
         }
-        mLiveTileTaskViewSimulator.setRecentsRotation(rotation);
+
         // If overview is in modal state when rotate, reset it to overview state without running
         // animation.
         if (mActivity.isInState(OVERVIEW_MODAL_TASK)) {
@@ -1800,8 +1798,6 @@
         requestLayout();
         // Reapply the current page to update page scrolls.
         setCurrentPage(mCurrentPage);
-        mLiveTileTaskViewSimulator.setLayoutRotation(getPagedViewOrientedState().getTouchRotation(),
-                getPagedViewOrientedState().getDisplayRotation());
     }
 
     public RecentsOrientedState getPagedViewOrientedState() {
@@ -2090,22 +2086,35 @@
         boolean launchingCenterTask = taskIndex == centerTaskIndex;
 
         float toScale = getMaxScaleForFullScreen();
+        RecentsView recentsView = tv.getRecentsView();
         if (launchingCenterTask) {
-            RecentsView recentsView = tv.getRecentsView();
             anim.play(ObjectAnimator.ofFloat(recentsView, RECENTS_SCALE_PROPERTY, toScale));
             anim.play(ObjectAnimator.ofFloat(recentsView, FULLSCREEN_PROGRESS, 1));
         } else {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
             float displacementX = tv.getWidth() * (toScale - tv.getCurveScale());
-            anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex), TRANSLATION_X,
-                    mIsRtl ? -displacementX : displacementX));
+            float primaryTranslation = mIsRtl ? -displacementX : displacementX;
+            anim.play(ObjectAnimator.ofFloat(getPageAt(centerTaskIndex),
+                    mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation));
+            int runningTaskIndex = recentsView.getRunningTaskIndex();
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && runningTaskIndex != -1
+                    && runningTaskIndex != taskIndex) {
+                anim.play(ObjectAnimator.ofFloat(
+                        recentsView.getLiveTileTaskViewSimulator().taskPrimaryTranslation,
+                        AnimatedFloat.VALUE,
+                        primaryTranslation));
+            }
 
             int otherAdjacentTaskIndex = centerTaskIndex + (centerTaskIndex - taskIndex);
             if (otherAdjacentTaskIndex >= 0 && otherAdjacentTaskIndex < getPageCount()) {
-                anim.play(new PropertyListBuilder()
-                        .translationX(mIsRtl ? -displacementX : displacementX)
-                        .scale(1)
-                        .build(getPageAt(otherAdjacentTaskIndex)));
+                PropertyValuesHolder[] properties = new PropertyValuesHolder[3];
+                properties[0] = PropertyValuesHolder.ofFloat(
+                        mOrientationHandler.getPrimaryViewTranslate(), primaryTranslation);
+                properties[1] = PropertyValuesHolder.ofFloat(View.SCALE_X, 1);
+                properties[2] = PropertyValuesHolder.ofFloat(View.SCALE_Y, 1);
+
+                anim.play(ObjectAnimator.ofPropertyValuesHolder(getPageAt(otherAdjacentTaskIndex),
+                        properties));
             }
         }
         return anim;
@@ -2169,8 +2178,8 @@
             mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         }
-        mPendingAnimation.addEndListener((endState) -> {
-            if (endState.isSuccess) {
+        mPendingAnimation.addEndListener(isSuccess -> {
+            if (isSuccess) {
                 Consumer<Boolean> onLaunchResult = (result) -> {
                     onTaskLaunchAnimationEnd(result);
                     if (!result) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2db3b66..3230348 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -42,7 +42,6 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
@@ -72,7 +71,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-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;
@@ -194,12 +192,6 @@
 
     private boolean mEndQuickswitchCuj;
 
-    // Order in which the footers appear. Lower order appear below higher order.
-    public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
-    private final FooterWrapper[] mFooters = new FooterWrapper[2];
-    private float mFooterVerticalOffset = 0;
-    private float mFooterAlpha = 1;
-    private int mStackHeight;
     private View mContextualChipWrapper;
     private View mContextualChip;
     private final float[] mIconCenterCoords = new float[2];
@@ -331,6 +323,9 @@
      * @param modalness [0, 1] 0 being in context with other tasks, 1 being shown on its own.
      */
     public void setModalness(float modalness) {
+        if (mModalness == modalness) {
+            return;
+        }
         mModalness = modalness;
         mIconView.setAlpha(comp(modalness));
         if (mContextualChip != null) {
@@ -340,8 +335,7 @@
         if (mContextualChipWrapper != null) {
             mContextualChipWrapper.setAlpha(comp(modalness));
         }
-
-        updateFooterVerticalOffset(mFooterVerticalOffset);
+        mDigitalWellBeingToast.updateBannerOffset(modalness);
     }
 
     public TaskMenuView getMenuView() {
@@ -378,11 +372,9 @@
     }
 
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
-        final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
-                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
-        AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController();
-        currentAnimation.setEndAction(() -> pendingAnimation.finish(true));
-        return currentAnimation;
+        return getRecentsView().createTaskLaunchAnimation(
+                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR)
+                .createPlaybackController();
     }
 
     public void launchTask(boolean animate) {
@@ -560,8 +552,12 @@
                 .getInterpolation(progress);
         mIconView.setScaleX(scale);
         mIconView.setScaleY(scale);
-
-        updateFooterVerticalOffset(1.0f - scale);
+        if (mContextualChip != null && mContextualChipWrapper != null) {
+            mContextualChipWrapper.setAlpha(scale);
+            mContextualChip.setScaleX(scale);
+            mContextualChip.setScaleY(scale);
+        }
+        mDigitalWellBeingToast.updateBannerOffset(1f - scale);
     }
 
     public void setIconScaleAnimStartProgress(float startProgress) {
@@ -633,12 +629,9 @@
         mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
         setCurveScale(curveScaleForCurveInterpolation);
 
-        mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
-        for (FooterWrapper footer : mFooters) {
-            if (footer != null) {
-                footer.mView.setAlpha(mFooterAlpha);
-            }
-        }
+        float dwbBannerAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation,
+                0f, 1f);
+        mDigitalWellBeingToast.updateBannerAlpha(dwbBannerAlpha);
 
         if (mMenuView != null) {
             PagedOrientationHandler pagedOrientationHandler = getPagedOrientationHandler();
@@ -651,57 +644,6 @@
     }
 
     /**
-     * Sets the footer at the specific index and returns the previously set footer.
-     */
-    public View setFooter(int index, View view) {
-        View oldFooter = null;
-
-        // If the footer are is already collapsed, do not animate entry
-        boolean shouldAnimateEntry = mFooterVerticalOffset <= 0;
-
-        if (mFooters[index] != null) {
-            oldFooter = mFooters[index].mView;
-            mFooters[index].release();
-            removeView(oldFooter);
-
-            // If we are replacing an existing footer, do not animate entry
-            shouldAnimateEntry = false;
-        }
-        if (view != null) {
-            int indexToAdd = getChildCount();
-            for (int i = index - 1; i >= 0; i--) {
-                if (mFooters[i] != null) {
-                    indexToAdd = indexOfChild(mFooters[i].mView);
-                    break;
-                }
-            }
-
-            addView(view, indexToAdd);
-            LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
-            layoutParams.gravity = BOTTOM | CENTER_HORIZONTAL;
-            layoutParams.bottomMargin =
-                    ((MarginLayoutParams) mSnapshotView.getLayoutParams()).bottomMargin;
-            view.setAlpha(mFooterAlpha);
-            mFooters[index] = new FooterWrapper(view);
-            if (shouldAnimateEntry) {
-                mFooters[index].animateEntry();
-            }
-        } else {
-            mFooters[index] = null;
-        }
-
-        mStackHeight = 0;
-        for (FooterWrapper footer : mFooters) {
-            if (footer != null) {
-                footer.setVerticalShift(mStackHeight);
-                mStackHeight += footer.mExpectedHeight;
-            }
-        }
-
-        return oldFooter;
-    }
-
-    /**
      * Sets the contextual chip.
      *
      * @param view Wrapper view containing contextual chip.
@@ -774,24 +716,6 @@
             SYSTEM_GESTURE_EXCLUSION_RECT.get(0).set(0, 0, getWidth(), getHeight());
             setSystemGestureExclusionRects(SYSTEM_GESTURE_EXCLUSION_RECT);
         }
-
-        mStackHeight = 0;
-        for (FooterWrapper footer : mFooters) {
-            if (footer != null) {
-                mStackHeight += footer.mView.getHeight();
-            }
-        }
-        updateFooterVerticalOffset(0);
-    }
-
-    private void updateFooterVerticalOffset(float offset) {
-        mFooterVerticalOffset = offset;
-
-        for (FooterWrapper footer : mFooters) {
-            if (footer != null) {
-                footer.updateFooterOffset();
-            }
-        }
     }
 
     public static float getCurveScaleForInterpolation(float linearInterpolation) {
@@ -854,71 +778,6 @@
         }
     }
 
-    private class FooterWrapper extends ViewOutlineProvider {
-
-        final View mView;
-        final ViewOutlineProvider mOldOutlineProvider;
-        final ViewOutlineProvider mDelegate;
-
-        final int mExpectedHeight;
-        final int mOldPaddingBottom;
-
-        int mAnimationOffset = 0;
-        int mEntryAnimationOffset = 0;
-
-        public FooterWrapper(View view) {
-            mView = view;
-            mOldOutlineProvider = view.getOutlineProvider();
-            mDelegate = mOldOutlineProvider == null
-                    ? ViewOutlineProvider.BACKGROUND : mOldOutlineProvider;
-
-            mExpectedHeight = getExpectedViewHeight(view);
-            mOldPaddingBottom = view.getPaddingBottom();
-
-            if (mOldOutlineProvider != null) {
-                view.setOutlineProvider(this);
-                view.setClipToOutline(true);
-            }
-        }
-
-        public void setVerticalShift(int shift) {
-            mView.setPadding(mView.getPaddingLeft(), mView.getPaddingTop(),
-                    mView.getPaddingRight(), mOldPaddingBottom + shift);
-        }
-
-        @Override
-        public void getOutline(View view, Outline outline) {
-            mDelegate.getOutline(view, outline);
-            outline.offset(0, -mAnimationOffset - mEntryAnimationOffset);
-        }
-
-        void updateFooterOffset() {
-            float offset = Utilities.or(mFooterVerticalOffset, mModalness);
-            mAnimationOffset = Math.round(mStackHeight * offset);
-            mView.setTranslationY(mAnimationOffset + mEntryAnimationOffset
-                    + mCurrentFullscreenParams.mCurrentDrawnInsets.bottom
-                    + mCurrentFullscreenParams.mCurrentDrawnInsets.top);
-            mView.invalidateOutline();
-        }
-
-        void release() {
-            mView.setOutlineProvider(mOldOutlineProvider);
-            setVerticalShift(0);
-        }
-
-        void animateEntry() {
-            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-            animator.addUpdateListener(anim -> {
-               float factor = 1 - anim.getAnimatedFraction();
-               int totalShift = mExpectedHeight + mView.getPaddingBottom() - mOldPaddingBottom;
-                mEntryAnimationOffset = Math.round(factor * totalShift);
-                updateFooterOffset();
-            });
-            animator.setDuration(100);
-            animator.start();
-        }
-    }
-
     private int getExpectedViewHeight(View view) {
         int expectedHeight;
         int h = view.getLayoutParams().height;
diff --git a/res/layout/search_result_hero_app.xml b/res/layout/search_result_hero_app.xml
deleted file mode 100644
index bd0e42b..0000000
--- a/res/layout/search_result_hero_app.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.launcher3.views.HeroSearchResultView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:gravity="center_vertical"
-    android:padding="@dimen/dynamic_grid_edge_margin">
-
-    <FrameLayout
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_gravity="center_vertical"
-        android:layout_weight="1">
-        <com.android.launcher3.BubbleTextView
-            android:id="@+id/bubble_text"
-            style="@style/BaseIcon"
-            android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
-            android:gravity="start|center_vertical"
-            android:textAlignment="viewStart"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="16sp"
-            android:layout_height="wrap_content"
-            launcher:iconDisplay="hero_app"
-            launcher:layoutHorizontal="true"/>
-
-        <View
-            android:id="@+id/icon"
-            android:layout_width="@dimen/deep_shortcut_icon_size"
-            android:layout_height="@dimen/deep_shortcut_icon_size"
-            android:layout_gravity="start|center_vertical"
-            android:background="@drawable/ic_deepshortcut_placeholder"/>
-    </FrameLayout>
-
-    <com.android.launcher3.BubbleTextView
-        android:id="@+id/shortcut_0"
-        style="@style/BaseIcon"
-        android:layout_width="@dimen/deep_shortcut_icon_size"
-        android:layout_height="match_parent"
-        android:gravity="start|center_vertical"
-        android:textAlignment="center"
-        launcher:iconDisplay="shortcut_popup"
-        android:textSize="12sp"
-        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
-        launcher:layoutHorizontal="false"/>
-
-    <com.android.launcher3.BubbleTextView
-        android:id="@+id/shortcut_1"
-        style="@style/BaseIcon"
-        android:layout_width="@dimen/deep_shortcut_icon_size"
-        android:layout_height="match_parent"
-        android:gravity="start|center_vertical"
-        android:textAlignment="center"
-        launcher:iconDisplay="shortcut_popup"
-        android:textSize="12sp"
-        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
-        launcher:layoutHorizontal="false"/>
-
-</com.android.launcher3.views.HeroSearchResultView>
\ No newline at end of file
diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml
index ef3c8b2..81c23e4 100644
--- a/res/layout/search_result_icon_row.xml
+++ b/res/layout/search_result_icon_row.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Projectza
+<?xml version="1.0" encoding="utf-8"?><!-- 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.
@@ -14,18 +14,63 @@
 -->
 <com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/BaseIcon"
+    android:orientation="horizontal"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
-    android:gravity="start|center_vertical"
-    android:textAlignment="viewStart"
-    android:textColor="?android:attr/textColorPrimary"
-    android:textSize="16sp"
-    android:padding="@dimen/dynamic_grid_edge_margin"
-    launcher:iconDisplay="hero_app"
-    launcher:layoutHorizontal="true"
-    >
+    android:padding="@dimen/dynamic_grid_edge_margin">
+
+    <com.android.launcher3.views.SearchResultIcon
+        android:layout_width="wrap_content"
+        android:id="@+id/icon"
+        launcher:iconDisplay="hero_app"
+        android:layout_height="wrap_content" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_weight="1"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/dynamic_grid_edge_margin"
+        android:orientation="vertical"
+        android:layout_gravity="center_vertical">
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:id="@id/title"
+            android:layout_height="wrap_content"
+            android:gravity="start|center_vertical"
+            android:maxLines="1"
+            android:textAlignment="viewStart"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/search_hero_title_size" />
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:id="@+id/desc"
+            android:maxLines="1"
+            android:textColor="?android:attr/textColorTertiary"
+            android:textSize="@dimen/search_hero_subtitle_size"
+            android:layout_height="wrap_content" />
+    </LinearLayout>
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_0"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        android:gravity="start|center_vertical"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="@dimen/search_hero_subtitle_size"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false" />
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/shortcut_1"
+        style="@style/BaseIcon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="match_parent"
+        launcher:iconDisplay="shortcut_popup"
+        android:textSize="@dimen/search_hero_inline_button_size"
+        launcher:iconSizeOverride="@dimen/deep_shortcut_icon_size"
+        launcher:layoutHorizontal="false" />
 
 </com.android.launcher3.views.SearchResultIconRow>
\ No newline at end of file
diff --git a/res/layout/search_result_people_item.xml b/res/layout/search_result_people_item.xml
index a603941..7526f6f 100644
--- a/res/layout/search_result_people_item.xml
+++ b/res/layout/search_result_people_item.xml
@@ -29,6 +29,7 @@
         android:layout_width="0dp"
         android:textColor="?android:attr/textColorPrimary"
         android:id="@+id/title"
+        android:textSize="@dimen/search_hero_title_size"
         android:layout_height="wrap_content"
         android:layout_weight="1" />
 
diff --git a/res/layout/search_result_play_item.xml b/res/layout/search_result_play_item.xml
index cdb793c..d70c56a 100644
--- a/res/layout/search_result_play_item.xml
+++ b/res/layout/search_result_play_item.xml
@@ -16,7 +16,7 @@
 <com.android.launcher3.views.SearchResultPlayItem xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:padding="8dp"
+    android:padding="4dp"
     android:orientation="horizontal">
     <View
         android:id="@+id/icon"
@@ -31,13 +31,18 @@
         android:layout_gravity="start|center_vertical"
         android:layout_weight="1"
         android:orientation="vertical"
-        android:padding="8dp">
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"
+        android:paddingStart="8dp"
+        android:paddingEnd="8dp">
 
         <TextView
             android:id="@+id/title_view"
             style="@style/TextHeadline"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
+            android:maxLines="1"
+            android:ellipsize="end"
             android:textAlignment="viewStart"
             android:textColor="?android:attr/textColorPrimary"
             android:textSize="16sp" />
diff --git a/res/layout/search_result_settings_row.xml b/res/layout/search_result_settings_row.xml
index 19daf34..22c08bf 100644
--- a/res/layout/search_result_settings_row.xml
+++ b/res/layout/search_result_settings_row.xml
@@ -18,40 +18,43 @@
     android:background="?android:attr/selectableItemBackground"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:orientation="vertical"
+    android:orientation="horizontal"
     android:gravity="center_vertical"
-    android:padding="4dp"
-    android:minHeight="48dp"
-    android:textColor="?android:attr/textColorPrimary"
-    android:textSize="14sp">
+    android:padding="@dimen/dynamic_grid_cell_padding_x"
+    android:textColor="?android:attr/textColorPrimary">
 
-    <TextView
-        android:id="@+id/title"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
+    <View
+        android:layout_width="@dimen/search_settings_icon_size"
+        android:src="@drawable/ic_setting"
+        android:id="@+id/icon"
+        android:layout_height="@dimen/search_settings_icon_size" />
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:orientation="vertical"
+        android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
+        android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
         android:layout_height="wrap_content"
-        android:layout_marginBottom="4dp"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="16sp" />
-
-    <TextView
-        android:id="@+id/description"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="14sp" />
-
-    <TextView
-        android:id="@+id/breadcrumbs"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:alpha=".7"
-        android:textColor="?android:attr/textColorSecondary"
-        android:textSize="14sp" />
+        android:layout_weight="1">
 
 
+        <TextView
+            android:id="@+id/title"
+            style="@style/TextTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/search_line_spacing"
+            android:maxLines="1"
+            android:textColor="?android:attr/textColorPrimary"
+            android:textSize="@dimen/search_hero_title_size" />
+
+        <TextView
+            android:id="@+id/breadcrumbs"
+            style="@style/TextTitle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:visibility="gone"
+            android:textColor="?android:attr/textColorSecondary"
+            android:textSize="@dimen/search_hero_subtitle_size" />
+    </LinearLayout>
 </com.android.launcher3.views.SearchSettingsRowView>
\ No newline at end of file
diff --git a/res/layout/search_result_slice.xml b/res/layout/search_result_slice.xml
index ea1d49a..24d75e9 100644
--- a/res/layout/search_result_slice.xml
+++ b/res/layout/search_result_slice.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- 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.
@@ -13,7 +12,29 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<androidx.slice.widget.SliceView xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.SearchResultSettingsSlice xmlns:android="http://schemas.android.com/apk/res/android"
+    android:paddingHorizontal="@dimen/dynamic_grid_cell_padding_x"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingHorizontal="4dp" />
\ No newline at end of file
+    android:layout_height="wrap_content">
+
+    <FrameLayout
+        android:layout_width="wrap_content"
+        android:paddingTop="@dimen/search_settings_icon_vertical_offset"
+        android:layout_height="wrap_content">
+
+        <View
+            android:layout_width="@dimen/search_settings_icon_size"
+            android:src="@drawable/ic_setting"
+            android:id="@+id/icon"
+            android:layout_height="@dimen/search_settings_icon_size" />
+    </FrameLayout>
+
+    <androidx.slice.widget.SliceView
+        android:id="@+id/slice"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:layout_marginStart="@dimen/dynamic_grid_cell_padding_x"
+        android:layout_width="0dp" />
+
+</com.android.launcher3.views.SearchResultSettingsSlice>
+
diff --git a/res/layout/search_result_suggest.xml b/res/layout/search_result_suggest.xml
index a3227cb..01e25d5 100644
--- a/res/layout/search_result_suggest.xml
+++ b/res/layout/search_result_suggest.xml
@@ -12,7 +12,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.views.SearchResultSuggestion xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     style="@style/BaseIcon"
     android:layout_width="match_parent"
@@ -21,7 +21,7 @@
     android:gravity="start|center_vertical"
     android:textAlignment="viewStart"
     android:textColor="?android:attr/textColorPrimary"
-    android:textSize="16sp"
+    android:textSize="18sp"
     android:padding="@dimen/dynamic_grid_edge_margin"
     launcher:iconDisplay="hero_app"
     android:drawableTint="?android:attr/textColorPrimary"
@@ -29,7 +29,6 @@
     launcher:iconSizeOverride="24dp"
     launcher:matchTextInsetWithQuery="true"
     launcher:layoutHorizontal="true"
-    android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
-    >
+    android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding">
 
-</com.android.launcher3.views.SearchResultIconRow>
\ No newline at end of file
+</com.android.launcher3.views.SearchResultSuggestion>
\ No newline at end of file
diff --git a/res/layout/search_result_widget_live.xml b/res/layout/search_result_widget_live.xml
new file mode 100644
index 0000000..0dd8a06
--- /dev/null
+++ b/res/layout/search_result_widget_live.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.launcher3.views.SearchResultWidget android:layout_height="wrap_content"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:gravity="center"
+    android:layout_width="match_parent" />
\ No newline at end of file
diff --git a/res/layout/search_result_widget_preview.xml b/res/layout/search_result_widget_preview.xml
new file mode 100644
index 0000000..942b199
--- /dev/null
+++ b/res/layout/search_result_widget_preview.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.launcher3.views.SearchResultWidgetPreview xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:padding="@dimen/dynamic_grid_cell_padding_x"
+    android:layout_height="wrap_content">
+    <include layout="@layout/widget_cell" android:id="@+id/widget_cell"/>
+<!--    <include layout="@layout/widget_cell_content" />-->
+</com.android.launcher3.views.SearchResultWidgetPreview>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6b0f300..96c30b5 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
 /* Copyright 2008, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,13 +29,13 @@
     <attr name="isWorkspaceDarkText" format="boolean" />
     <attr name="workspaceTextColor" format="color" />
     <attr name="workspaceShadowColor" format="color" />
-    <attr name="workspaceAmbientShadowColor" format="color"/>
+    <attr name="workspaceAmbientShadowColor" format="color" />
     <attr name="workspaceKeyShadowColor" format="color" />
     <attr name="workspaceStatusBarScrim" format="reference" />
     <attr name="widgetsTheme" format="reference" />
     <attr name="loadingIconColor" format="color" />
-    <attr name="iconOnlyShortcutColor" format="color"/>
-    <attr name="eduHalfSheetBGColor" format="color"/>
+    <attr name="iconOnlyShortcutColor" format="color" />
+    <attr name="eduHalfSheetBGColor" format="color" />
 
     <attr name="folderDotColor" format="color" />
     <attr name="folderFillColor" format="color" />
@@ -69,13 +68,12 @@
         <attr name="folderDotColor" />
     </declare-styleable>
 
-    <declare-styleable name="SearchResultIconRow">
+    <declare-styleable name="SearchResultSuggestion">
         <attr name="customIcon" format="reference" />
         <attr name="matchTextInsetWithQuery" format="boolean" />
     </declare-styleable>
 
 
-
     <declare-styleable name="ShadowInfo">
         <attr name="ambientShadowColor" format="color" />
         <attr name="ambientShadowBlur" format="dimension" />
@@ -167,13 +165,21 @@
         <attr name="android:src" />
         <attr name="android:shadowColor" />
         <attr name="android:elevation" />
-        <attr name="darkTintColor" format="color"/>
+        <attr name="darkTintColor" format="color" />
     </declare-styleable>
 
     <declare-styleable name="RecyclerViewFastScroller">
         <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/values/dimens.xml b/res/values/dimens.xml
index f59f02f..7df3f77 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -248,4 +248,12 @@
     <!-- Onboarding bottomsheet related -->
     <dimen name="bottom_sheet_edu_padding">24dp</dimen>
 
+    <!-- Search related -->
+    <dimen name="search_hero_title_size">16sp</dimen>
+    <dimen name="search_hero_subtitle_size">15sp</dimen>
+    <dimen name="search_hero_inline_button_size">12sp</dimen>
+    <dimen name="search_settings_icon_size">36dp</dimen>
+    <dimen name="search_settings_icon_vertical_offset">16dp</dimen>
+    <dimen name="search_line_spacing">4dp</dimen>
+
 </resources>
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/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 3fa9b0a..836ded5 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -59,4 +59,4 @@
 
 LOCAL_ROBOTEST_TIMEOUT := 36000
 
-include prebuilts/misc/common/robolectric/4.3.1/run_robotests.mk
+include prebuilts/misc/common/robolectric/4.4/run_robotests.mk
diff --git a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
index b7ba106..2a94d9b 100644
--- a/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
+++ b/robolectric_tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
@@ -25,16 +25,20 @@
 
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.util.Executors;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
 
 import java.util.ArrayList;
 
 @RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
 public final class FolderNameProviderTest {
     private Context mContext;
     private WorkspaceItemInfo mItem1;
@@ -58,18 +62,20 @@
     }
 
     @Test
-    public void getSuggestedFolderName_workAssignedToEnd() {
+    public void getSuggestedFolderName_workAssignedToEnd() throws Exception {
         ArrayList<WorkspaceItemInfo> list = new ArrayList<>();
         list.add(mItem1);
         list.add(mItem2);
         FolderNameInfos nameInfos = new FolderNameInfos();
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        Executors.MODEL_EXECUTOR.submit(() ->
+                new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get();
         assertEquals("Work", nameInfos.getLabels()[0]);
 
         nameInfos.setLabel(0, "candidate1", 1.0f);
         nameInfos.setLabel(1, "candidate2", 1.0f);
         nameInfos.setLabel(2, "candidate3", 1.0f);
-        new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos);
+        Executors.MODEL_EXECUTOR.submit(() ->
+                new FolderNameProvider().getSuggestedFolderName(mContext, list, nameInfos)).get();
         assertEquals("Work", nameInfos.getLabels()[3]);
         assertTrue(nameInfos.hasSuggestions());
         assertTrue(nameInfos.hasPrimary());
diff --git a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
index c892618..01b23ba 100644
--- a/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
+++ b/robolectric_tests/src/com/android/launcher3/logging/FileLogTest.java
@@ -9,8 +9,8 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.Shadows;
-import org.robolectric.util.Scheduler;
+import org.robolectric.annotation.LooperMode;
+import org.robolectric.annotation.LooperMode.Mode;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -21,11 +21,10 @@
  * Tests for {@link FileLog}
  */
 @RunWith(RobolectricTestRunner.class)
+@LooperMode(Mode.PAUSED)
 public class FileLogTest {
 
     private File mTempDir;
-    private boolean mTestActive;
-
     @Before
     public void setUp() {
         int count = 0;
@@ -35,14 +34,6 @@
         } while (!mTempDir.mkdir());
 
         FileLog.setDir(mTempDir);
-
-        mTestActive = true;
-        Scheduler scheduler = Shadows.shadowOf(FileLog.getHandler().getLooper()).getScheduler();
-        new Thread(() -> {
-            while (mTestActive) {
-                scheduler.advanceToLastPostedRunnable();
-            }
-        }).start();
     }
 
     @After
@@ -52,8 +43,6 @@
             new File(mTempDir, "log-" + i).delete();
         }
         mTempDir.delete();
-
-        mTestActive = false;
     }
 
     @Test
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index 87fe3c0..aab6c25 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -30,6 +30,7 @@
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.shadows.ShadowLooperExecutor;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -43,8 +44,8 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.LooperMode;
 import org.robolectric.annotation.LooperMode.Mode;
+import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowPackageManager;
-import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -74,7 +75,8 @@
         // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
         // so that we can wait appropriately for the loader to complete.
         mTempMainExecutor = new LooperExecutor(createAndStartNewForegroundLooper("tempMain"));
-        ReflectionHelpers.setField(mModelHelper.getModel(), "mMainExecutor", mTempMainExecutor);
+        ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR);
+        sle.setHandler(mTempMainExecutor.getHandler());
     }
 
     @Test
diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
index baae2a6..e3694ae 100644
--- a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
+++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
@@ -59,8 +59,6 @@
     private InvariantDeviceProfile mIdp;
     private LauncherModelHelper mModelHelper;
 
-    private LauncherLayoutBuilder mLayoutBuilder;
-
     @Before
     public void setup() throws Exception {
         mModelHelper = new LauncherModelHelper();
@@ -70,7 +68,6 @@
                 Settings.Global.WINDOW_ANIMATION_SCALE, 0);
 
         mModelHelper.installApp(TEST_PACKAGE);
-        mLayoutBuilder = new LauncherLayoutBuilder();
     }
 
     @Test
@@ -86,7 +83,7 @@
     public void testAllAppsList_workProfile() throws Exception {
         ShadowUserManager sum = Shadow.extract(mTargetContext.getSystemService(UserManager.class));
         sum.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
-        sum.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+        sum.addProfile(SYSTEM_USER, WORK_PROFILE_ID, "work", FLAG_PROFILE);
 
         SecondaryDisplayLauncher launcher = loadLauncher();
         launcher.showAppDrawer(true);
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
index a3b7dc7..57eda7e 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/ShadowLooperExecutor.java
@@ -23,6 +23,8 @@
 
 import android.os.Handler;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.util.LooperExecutor;
 
 import org.robolectric.annotation.Implementation;
@@ -37,8 +39,13 @@
 
     @RealObject private LooperExecutor mRealExecutor;
 
+    private Handler mOverriddenHandler;
+
     @Implementation
     protected Handler getHandler() {
+        if (mOverriddenHandler != null) {
+            return mOverriddenHandler;
+        }
         Handler handler = directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
         Thread thread = handler.getLooper().getThread();
         if (!thread.isAlive()) {
@@ -49,4 +56,8 @@
         }
         return directlyOn(mRealExecutor, LooperExecutor.class, "getHandler");
     }
+
+    public void setHandler(@Nullable Handler handler) {
+        mOverriddenHandler = handler;
+    }
 }
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index f2b3071..849f98b 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -48,10 +48,12 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.shadows.ShadowLooperExecutor;
 
 import org.mockito.ArgumentCaptor;
 import org.robolectric.Robolectric;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowContentResolver;
 import org.robolectric.shadows.ShadowPackageManager;
 import org.robolectric.util.ReflectionHelpers;
@@ -403,14 +405,16 @@
     public void loadModelSync() throws ExecutionException, InterruptedException {
         // Since robolectric tests run on main thread, we run the loader-UI calls on a temp thread,
         // so that we can wait appropriately for the loader to complete.
-        ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.UI_HELPER_EXECUTOR);
+        ShadowLooperExecutor sle = Shadow.extract(Executors.MAIN_EXECUTOR);
+        sle.setHandler(Executors.UI_HELPER_EXECUTOR.getHandler());
 
         Callbacks mockCb = mock(Callbacks.class);
         getModel().addCallbacksAndLoad(mockCb);
 
         Executors.MODEL_EXECUTOR.submit(() -> { }).get();
         Executors.UI_HELPER_EXECUTOR.submit(() -> { }).get();
-        ReflectionHelpers.setField(getModel(), "mMainExecutor", Executors.MAIN_EXECUTOR);
+
+        sle.setHandler(null);
         getModel().removeCallbacks(mockCb);
     }
 
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 168d9c4..bc3e341 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -36,7 +36,7 @@
     private static final Rect sTmpRect = new Rect();
 
     // Represents the cell size on the grid in the two orientations.
-    private static final MainThreadInitializedObject<Point[]> CELL_SIZE =
+    public static final MainThreadInitializedObject<Point[]> CELL_SIZE =
             new MainThreadInitializedObject<>(c -> {
                 InvariantDeviceProfile inv = LauncherAppState.getIDP(c);
                 return new Point[] {inv.landscapeProfile.getCellSize(),
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
index a55c90d..8bf027d 100644
--- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
+++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java
@@ -86,13 +86,12 @@
             long mainProfileId = UserCache.INSTANCE.get(context)
                     .getSerialNumberForUser(myUserHandle());
             String oldWidgetId = Integer.toString(oldWidgetIds[i]);
-            int result = new ContentWriter(context, new ContentWriter.CommitParams(
-                    "appWidgetId=? and (restored & 1) = 1 and profileId=?",
-                    new String[] { oldWidgetId, Long.toString(mainProfileId) }))
+            final String where = "appWidgetId=? and (restored & 1) = 1 and profileId=?";
+            final String[] args = new String[] { oldWidgetId, Long.toString(mainProfileId) };
+            int result = new ContentWriter(context, new ContentWriter.CommitParams(where, args))
                     .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
                     .put(LauncherSettings.Favorites.RESTORED, state)
                     .commit();
-
             if (result == 0) {
                 Cursor cursor = cr.query(Favorites.CONTENT_URI,
                         new String[] {Favorites.APPWIDGET_ID},
@@ -106,6 +105,11 @@
                     cursor.close();
                 }
             }
+            // attempt to update widget id in backup table as well
+            new ContentWriter(context, ContentWriter.CommitParams.backupCommitParams(where, args))
+                    .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i])
+                    .put(LauncherSettings.Favorites.RESTORED, state)
+                    .commit();
         }
 
         LauncherAppState app = LauncherAppState.getInstanceNoCreate();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 6245637..3eb52ad 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -171,6 +171,8 @@
 
     private IconLoadRequest mIconLoadRequest;
 
+    private boolean mEnableIconUpdateAnimation = false;
+
     public BubbleTextView(Context context) {
         this(context, null, 0);
     }
@@ -446,6 +448,7 @@
             canvas.restoreToCount(count);
         }
         super.onDraw(canvas);
+        drawDotIfNecessary(canvas);
     }
 
     protected void drawFocusHighlight(Canvas canvas) {
@@ -696,6 +699,10 @@
         applyCompoundDrawables(icon);
     }
 
+    protected boolean iconUpdateAnimationEnabled() {
+        return mEnableIconUpdateAnimation;
+    }
+
     protected void applyCompoundDrawables(Drawable icon) {
         // If we had already set an icon before, disable relayout as the icon size is the
         // same as before.
@@ -706,7 +713,9 @@
         updateIcon(icon);
 
         // If the current icon is a placeholder color, animate its update.
-        if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable) {
+        if (mIcon != null
+                && mIcon instanceof PlaceHolderIconDrawable
+                && iconUpdateAnimationEnabled()) {
             animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon);
         }
 
@@ -728,6 +737,7 @@
         if (getTag() == info) {
             mIconLoadRequest = null;
             mDisableRelayout = true;
+            mEnableIconUpdateAnimation = true;
 
             // Optimization: Starting in N, pre-uploads the bitmap to RenderThread.
             info.bitmap.icon.prepareToDraw();
@@ -744,6 +754,7 @@
             }
 
             mDisableRelayout = false;
+            mEnableIconUpdateAnimation = false;
         }
     }
 
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/Launcher.java b/src/com/android/launcher3/Launcher.java
index ab8d7a5..058eca8 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -111,6 +111,7 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
+import com.android.launcher3.allapps.search.LiveSearchManager;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -276,6 +277,8 @@
 
     private LifecycleRegistry mLifecycleRegistry;
 
+    private LiveSearchManager mLiveSearchManager;
+
     @Thunk
     Workspace mWorkspace;
     @Thunk
@@ -389,6 +392,8 @@
         mIconCache = app.getIconCache();
         mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);
 
+        mLiveSearchManager = new LiveSearchManager(this);
+
         mDragController = new DragController(this);
         mAllAppsController = new AllAppsTransitionController(this);
         mStateManager = new StateManager<>(this, NORMAL);
@@ -492,6 +497,10 @@
         return mLifecycleRegistry;
     }
 
+    public LiveSearchManager getLiveSearchManager() {
+        return mLiveSearchManager;
+    }
+
     protected LauncherOverlayManager getDefaultOverlay() {
         return new LauncherOverlayManager() { };
     }
@@ -1583,6 +1592,7 @@
         mAppTransitionManager.unregisterRemoteAnimations();
         mUserChangedCallbackCloseable.close();
         mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
+        mLiveSearchManager.stop();
     }
 
     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
@@ -1829,7 +1839,7 @@
                 mTouchInProgress = true;
                 break;
             case MotionEvent.ACTION_UP:
-                mLastTouchUpTime = ev.getEventTime();
+                mLastTouchUpTime = SystemClock.uptimeMillis();
                 // Follow through
             case MotionEvent.ACTION_CANCEL:
                 mTouchInProgress = false;
@@ -2330,7 +2340,9 @@
             if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) {
                 // Verify that we own the widget
                 if (appWidgetInfo == null) {
-                    FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId);
+                    FileLog.e(TAG, "Removing invalid widget: id=" + item.appWidgetId
+                            + ",title=" + item.title
+                            + ",providerName=" + item.providerName.toShortString());
                     getModelWriter().deleteWidgetInfo(item, getAppWidgetHost());
                     return null;
                 }
@@ -2487,6 +2499,7 @@
     @Override
     public void bindAllApplications(AppInfo[] apps, int flags) {
         mAppsView.getAppsStore().setApps(apps, flags);
+        PopupContainerWithArrow.dismissInvalidPopup(this);
     }
 
     /**
@@ -2518,6 +2531,7 @@
     public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
         if (!updated.isEmpty()) {
             mWorkspace.updateShortcuts(updated);
+            PopupContainerWithArrow.dismissInvalidPopup(this);
         }
     }
 
@@ -2542,6 +2556,7 @@
     public void bindWorkspaceComponentsRemoved(final ItemInfoMatcher matcher) {
         mWorkspace.removeItemsByMatcher(matcher);
         mDragController.onAppsRemoved(matcher);
+        PopupContainerWithArrow.dismissInvalidPopup(this);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index d9cf7f1..803f8d2 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
 import android.util.IntProperty;
@@ -29,8 +32,8 @@
      */
     public static final int SPRING_LOADED_EXIT_DELAY = 500;
 
-    // The progress of an animation to all apps must be at least this far along to snap to all apps.
-    public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
+    // Progress after which the transition is assumed to be a success
+    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
     public static final IntProperty<Drawable> DRAWABLE_ALPHA =
             new IntProperty<Drawable>("drawableAlpha") {
@@ -131,4 +134,23 @@
                             return view.getAlpha();
                         }
                     };
+
+    /**
+     * Utility method to create an {@link AnimatorListener} which executes a callback on animation
+     * cancel.
+     */
+    public static AnimatorListener newCancelListener(Runnable callback) {
+        return new AnimatorListenerAdapter() {
+
+            boolean mDispatched = false;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                if (!mDispatched) {
+                    mDispatched = true;
+                    callback.run();
+                }
+            }
+        };
+    }
 }
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index aeed16a..6af248c 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -85,6 +85,7 @@
 import java.util.Locale;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
@@ -931,6 +932,11 @@
             final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db,
                     Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
                     "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
+            final String allWidgetIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+                    .collect(Collectors.joining(","));
+            final String validWidgetIds = validWidgets.getArray().toConcatString();
+            FileLog.d(TAG, "All widget ids: " + allWidgetIds);
+            FileLog.d(TAG, "Valid widget ids: " + validWidgetIds);
             for (int widgetId : allWidgets) {
                 if (!validWidgets.contains(widgetId)) {
                     try {
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index d2758f5..fe423ed 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -121,6 +121,12 @@
                 + LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
 
         /**
+         * The content:// style URL for "favorites_bakup" table
+         */
+        public static final Uri BACKUP_CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + BACKUP_TABLE_NAME);
+
+        /**
          * The content:// style URL for "favorites_preview" table
          */
         public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index dfff358..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");
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index f773191..3c88288 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -37,7 +37,6 @@
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.slice.widget.SliceView;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
@@ -47,7 +46,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.views.SearchSliceWrapper;
+import com.android.launcher3.views.SearchResultWidget;
 import com.android.systemui.plugins.shared.SearchTarget;
 
 import java.util.List;
@@ -75,8 +74,6 @@
 
     public static final int VIEW_TYPE_SEARCH_CORPUS_TITLE = 1 << 5;
 
-    public static final int VIEW_TYPE_SEARCH_HERO_APP = 1 << 6;
-
     public static final int VIEW_TYPE_SEARCH_ROW_WITH_BUTTON = 1 << 7;
 
     public static final int VIEW_TYPE_SEARCH_ROW = 1 << 8;
@@ -93,6 +90,10 @@
 
     public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
 
+    public static final int VIEW_TYPE_SEARCH_WIDGET_LIVE = 1 << 15;
+
+    public static final int VIEW_TYPE_SEARCH_WIDGET_PREVIEW = 1 << 16;
+
     // Common view type masks
     public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
     public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON;
@@ -178,7 +179,6 @@
 
         boolean isCountedForAccessibility() {
             return viewType == VIEW_TYPE_ICON
-                    || viewType == VIEW_TYPE_SEARCH_HERO_APP
                     || viewType == VIEW_TYPE_SEARCH_ROW_WITH_BUTTON
                     || viewType == VIEW_TYPE_SEARCH_SLICE
                     || viewType == VIEW_TYPE_SEARCH_ROW
@@ -289,7 +289,8 @@
             int viewType = mApps.getAdapterItems().get(position).viewType;
             if (isIconViewType(viewType)) {
                 return 1 * SPAN_MULTIPLIER;
-            } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL) {
+            } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL
+                    || viewType == VIEW_TYPE_SEARCH_WIDGET_PREVIEW) {
                 return mAppsPerRow;
             } else {
                 // Section breaks span the full width
@@ -411,9 +412,6 @@
             case VIEW_TYPE_SEARCH_CORPUS_TITLE:
                 return new ViewHolder(
                         mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
-            case VIEW_TYPE_SEARCH_HERO_APP:
-                return new ViewHolder(mLayoutInflater.inflate(
-                        R.layout.search_result_hero_app, parent, false));
             case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_play_item, parent, false));
@@ -435,6 +433,12 @@
             case VIEW_TYPE_SEARCH_SUGGEST:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_suggest, parent, false));
+            case VIEW_TYPE_SEARCH_WIDGET_LIVE:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_widget_live, parent, false));
+            case VIEW_TYPE_SEARCH_WIDGET_PREVIEW:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_widget_preview, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -468,23 +472,17 @@
                     searchView.setVisibility(View.GONE);
                 }
                 break;
-            case VIEW_TYPE_SEARCH_SLICE:
-                SliceView sliceView = (SliceView) holder.itemView;
-                SearchAdapterItem slicePayload = (SearchAdapterItem) mApps.getAdapterItems().get(
-                        position);
-                SearchTarget searchTarget = slicePayload.getSearchTarget();
-                sliceView.setTag(new SearchSliceWrapper(mLauncher, sliceView, searchTarget));
-
-                break;
             case VIEW_TYPE_SEARCH_CORPUS_TITLE:
             case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
-            case VIEW_TYPE_SEARCH_HERO_APP:
+            case VIEW_TYPE_SEARCH_SLICE:
             case VIEW_TYPE_SEARCH_ROW:
             case VIEW_TYPE_SEARCH_ICON:
             case VIEW_TYPE_SEARCH_ICON_ROW:
             case VIEW_TYPE_SEARCH_PEOPLE:
             case VIEW_TYPE_SEARCH_THUMBNAIL:
             case VIEW_TYPE_SEARCH_SUGGEST:
+            case VIEW_TYPE_SEARCH_WIDGET_LIVE:
+            case VIEW_TYPE_SEARCH_WIDGET_PREVIEW:
                 SearchAdapterItem item =
                         (SearchAdapterItem) mApps.getAdapterItems().get(position);
                 SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView;
@@ -503,12 +501,8 @@
         if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
             ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
         }
-        if (holder.itemView instanceof SliceView) {
-            SliceView sliceView = (SliceView) holder.itemView;
-            if (sliceView.getTag() instanceof SearchSliceWrapper) {
-                ((SearchSliceWrapper) sliceView.getTag()).destroy();
-            }
-            sliceView.setTag(null);
+        if (holder.itemView instanceof SearchResultWidget) {
+            ((SearchResultWidget) holder.itemView).removeListener();
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
index d7af5f1..93da1c0 100644
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
+import android.annotation.TargetApi;
 import android.graphics.Insets;
 import android.os.Build;
 import android.util.Log;
@@ -26,8 +27,9 @@
 import android.view.animation.LinearInterpolator;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.os.BuildCompat;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.UiThreadHelper;
 
 /**
  * Handles IME over all apps to be synchronously transitioning along with the passed in
@@ -35,7 +37,7 @@
  */
 public class AllAppsInsetTransitionController {
 
-    private static final boolean DEBUG = false;
+    private static final boolean DEBUG = true;
     private static final String TAG = "AllAppsInsetTransitionController";
     private static final Interpolator LINEAR = new LinearInterpolator();
 
@@ -53,17 +55,30 @@
     private float mDown, mCurrent;
     private View mApps;
 
+    // Only purpose of these states is to keep track of fast fling transition
+    enum State {
+        RESET, DRAG_START_BOTTOM, DRAG_START_BOTTOM_IME_CANCELLED,
+        FLING_END_TOP, FLING_END_TOP_IME_CANCELLED,
+        DRAG_START_TOP, FLING_END_BOTTOM
+    }
+    private State mState;
+
     public AllAppsInsetTransitionController(float allAppsHeight, View appsView) {
         mAllAppsHeight = allAppsHeight;
         mApps = appsView;
     }
 
     public void hide() {
-        if (!BuildCompat.isAtLeastR()) return;
+        if (!Utilities.ATLEAST_R) return;
 
         WindowInsets insets = mApps.getRootWindowInsets();
         if (insets == null) return;
 
+        boolean imeVisible = insets.isVisible(WindowInsets.Type.ime());
+
+        if (DEBUG) {
+            Log.d(TAG, "\nhide imeVisible=" +  imeVisible);
+        }
         if (insets.isVisible(WindowInsets.Type.ime())) {
             mApps.getWindowInsetsController().hide(WindowInsets.Type.ime());
         }
@@ -75,23 +90,26 @@
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = Build.VERSION_CODES.R)
+    @TargetApi(Build.VERSION_CODES.R)
     public void onDragStart(float progress) {
-        if (!BuildCompat.isAtLeastR()) return;
-        onAnimationEnd(progress);
+        if (!Utilities.ATLEAST_R) return;
 
+        // Until getRootWindowInsets().isVisible(...) method returns correct value,
+        // only support InsetController based IME transition during swipe up and
+        // NOT swipe down
+        if (Float.compare(progress, 0f) == 0) return;
+
+        setState(true, false, progress);
         mDown = progress * mAllAppsHeight;
 
         // Below two values are sometimes incorrect. Possibly a platform bug
-        mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
-        mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
+        // mDownInsetBottom = mApps.getRootWindowInsets().getInsets(WindowInsets.Type.ime()).bottom;
+        // mShownAtDown = mApps.getRootWindowInsets().isVisible(WindowInsets.Type.ime());
 
-        // override this value based on what it should actually be.
-        mShownAtDown = Float.compare(progress, 1f) == 0 ? false : true;
-        mDownInsetBottom = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
         if (DEBUG) {
-            Log.d(TAG, "\nonDragStart mDownInsets=" + mDownInsetBottom
-                    + " mShownAtDown =" + mShownAtDown);
+            Log.d(TAG, "\nonDragStart progress=" +  progress
+                    + " mDownInsets=" + mDownInsetBottom
+                    + " mShownAtDown=" + mShownAtDown);
         }
 
         mApps.getWindowInsetsController().controlWindowInsetsAnimation(
@@ -103,44 +121,69 @@
                         if (DEBUG) {
                             Log.d(TAG, "Listener.onReady " + (mCurrentRequest == this));
                         }
-                        if (mCurrentRequest == this) {
-                            mAnimationController = controller;
-                        } else {
-                            controller.finish(mShownAtDown);
+                        if (controller != null) {
+                            if (mCurrentRequest == this && !handleFinishOnFling(controller)) {
+                                    mAnimationController = controller;
+                            } else {
+                                controller.finish(false /* just don't show */);
+                            }
                         }
                     }
 
                     @Override
                     public void onFinished(WindowInsetsAnimationController controller) {
                         // when screen lock happens, then this method get called
-                        mAnimationController.finish(false);
-                        mAnimationController = null;
                         if (DEBUG) {
-                            Log.d(TAG, "Listener.onFinished ctrl=" + controller);
+                            Log.d(TAG, "Listener.onFinished ctrl=" + controller
+                                    + " mAnimationController=" + mAnimationController);
+                        }
+                        if (mAnimationController != null) {
+                            mAnimationController.finish(true);
+                            mAnimationController = null;
                         }
                     }
 
                     @Override
                     public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
+                        if (DEBUG) {
+                            Log.d(TAG, "Listener.onCancelled ctrl=" + controller
+                                    + " mAnimationController=" + mAnimationController);
+                        }
+                        if (mState == State.DRAG_START_BOTTOM) {
+                            mState = State.DRAG_START_BOTTOM_IME_CANCELLED;
+                        }
                         mAnimationController = null;
                         if (controller != null) {
-                            controller.finish(mShownAtDown);
-                        }
-                        if (DEBUG) {
-                            Log.d(TAG, "Listener.onCancelled ctrl=" + controller);
+                            controller.finish(true);
                         }
                     }
                 });
     }
 
     /**
+     * If IME bounds after touch sequence finishes, call finish.
+     */
+    private boolean handleFinishOnFling(WindowInsetsAnimationController controller) {
+        if (!Utilities.ATLEAST_R) return false;
+
+        if (mState == State.FLING_END_TOP) {
+            controller.finish(true);
+            return true;
+        } else if (mState == State.FLING_END_BOTTOM) {
+            controller.finish(false);
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Handles the translation using the progress.
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = 30)
+    @TargetApi(Build.VERSION_CODES.R)
     public void setProgress(float progress) {
-        if (!BuildCompat.isAtLeastR()) return;
+        if (!Utilities.ATLEAST_R) return;
         // progress that equals to 0 or 1 is error prone. Do not use them.
         // Instead use onDragStart and onAnimationEnd
         if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
@@ -153,18 +196,17 @@
 
         int inset = (int) (mDownInsetBottom + (mDown - mCurrent) - shift);
 
-        if (DEBUG) {
-            Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
-                    + mDown + " hidden=" + mHiddenInsetBottom
-                    + " shown=" + mShownInsetBottom
-                    + " mDownInsets.bottom=" + mDownInsetBottom + " inset:" + inset
-                    + " shift: " + shift);
-        }
         final int start = mShownAtDown ? mShownInsetBottom : mHiddenInsetBottom;
         final int end = mShownAtDown ? mHiddenInsetBottom : mShownInsetBottom;
         inset = Math.max(inset, mHiddenInsetBottom);
         inset = Math.min(inset, mShownInsetBottom);
-        Log.d(TAG, "updateInset inset:" + inset);
+        if (DEBUG && false) {
+            Log.d(TAG, "updateInset mCurrent=" + mCurrent + " mDown="
+                    + mDown + " hidden=" + mHiddenInsetBottom
+                    + " shown=" + mShownInsetBottom
+                    + " mDownInsets.bottom=" + mDownInsetBottom + " inset=" + inset
+                    + " shift= " + shift);
+        }
 
         mAnimationController.setInsetsAndAlpha(
                 Insets.of(0, 0, 0, inset),
@@ -176,22 +218,73 @@
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = 30)
+    @TargetApi(Build.VERSION_CODES.R)
     public void onAnimationEnd(float progress) {
         if (DEBUG) {
+            Log.d(TAG, "onAnimationEnd progress=" + progress
+                    + " mAnimationController=" + mAnimationController);
+        }
+        if (mState == null) {
+            // only called when launcher restarting.
+            UiThreadHelper.hideKeyboardAsync(mApps.getContext(), mApps.getWindowToken());
+        }
+
+        setState(false, true, progress);
+
+
+        if (mAnimationController == null) {
+            if (mState == State.FLING_END_TOP_IME_CANCELLED) {
+                mApps.getWindowInsetsController().show(WindowInsets.Type.ime());
+            }
+            return;
+        }
+
+        /* handle finish */
+        if (mState == State.FLING_END_TOP) {
+            mAnimationController.finish(true /* show */);
+        } else {
+            if (Float.compare(progress, 1f) == 0 /* bottom */) {
+                mAnimationController.finish(false /* gone */);
+            } else {
+                mAnimationController.finish(mShownAtDown);
+            }
+        }
+        /* handle finish */
+
+        if (DEBUG) {
             Log.d(TAG, "endTranslation progress=" + progress
                     + " mAnimationController=" + mAnimationController);
         }
-
-        if (mAnimationController == null) return;
-
-        if (Float.compare(progress, 1f) == 0 /* bottom */) {
-            mAnimationController.finish(false /* gone */);
-        }
-        if (Float.compare(progress, 0f) == 0 /* top */) {
-            mAnimationController.finish(true /* show */);
-        }
         mAnimationController = null;
         mCurrentRequest = null;
+        setState(false, false, progress);
+    }
+
+    private void setState(boolean start, boolean end, float progress) {
+        State state = State.RESET;
+        if (start && end) {
+            throw new IllegalStateException("drag start and end cannot happen in same call");
+        }
+        if (start) {
+            if (Float.compare(progress, 1f) == 0) {
+                state = State.DRAG_START_BOTTOM;
+            } else if (Float.compare(progress, 0f) == 0) {
+                state = State.DRAG_START_TOP;
+            }
+        } else if (end) {
+            if (Float.compare(progress, 1f) == 0 && mState == State.DRAG_START_TOP) {
+                state = State.FLING_END_BOTTOM;
+            } else if (Float.compare(progress, 0f) == 0) {
+                if (mState == State.DRAG_START_BOTTOM) {
+                    state = State.FLING_END_TOP;
+                } else if (mState == State.DRAG_START_BOTTOM_IME_CANCELLED) {
+                    state = State.FLING_END_TOP_IME_CANCELLED;
+                }
+            }
+        }
+        if (DEBUG) {
+            Log.d(TAG, "setState " + mState + " -> " + state);
+        }
+        mState = state;
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index 3c81811..1d31975 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -98,7 +98,8 @@
                     mAppsView.getActiveRecyclerView().getLayoutManager();
             if (layoutManager.findFirstVisibleItemPosition() <= index
                     && index < parent.getChildCount()) {
-                decorationHandler.onFocusDraw(c, parent.getChildAt(index));
+                RecyclerView.ViewHolder vh = parent.findViewHolderForAdapterPosition(index);
+                if (vh != null) decorationHandler.onFocusDraw(c, vh.itemView);
             }
         }
         decorationHandler.reset();
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0c488a6..f9ab196 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -264,11 +264,15 @@
         if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()) {
             mInsetController.onAnimationEnd(mProgress);
             if (Float.compare(mProgress, 0f) == 0) {
+                mLauncher.getLiveSearchManager().start();
                 EditText editText = mAppsView.getSearchUiManager().getEditText();
                 if (editText != null) {
                     editText.requestFocus();
                 }
             }
+            else {
+                mLauncher.getLiveSearchManager().stop();
+            }
             // TODO: should make the controller hide synchronously
         }
     }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 2450787..1dc10fe 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -132,6 +132,9 @@
      * Returns the child adapter item with IME launch focus.
      */
     public AdapterItem getFocusedChild() {
+        if (mAdapterItems.size() == 0 || getFocusedChildIndex() == -1) {
+            return null;
+        }
         return mAdapterItems.get(getFocusedChildIndex());
     }
 
diff --git a/src/com/android/launcher3/allapps/search/LiveSearchManager.java b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
new file mode 100644
index 0000000..ec33908
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/LiveSearchManager.java
@@ -0,0 +1,150 @@
+/*
+ * 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.launcher3.allapps.search;
+
+import static com.android.launcher3.widget.WidgetHostViewLoader.getDefaultOptionsForWidget;
+
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.lifecycle.LiveData;
+import androidx.slice.Slice;
+import androidx.slice.widget.SliceLiveData;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+
+import java.util.HashMap;
+
+/**
+ * Manages Lifecycle for Live search results
+ */
+public class LiveSearchManager {
+
+    public static final int SEARCH_APPWIDGET_HOST_ID = 2048;
+
+    private final Launcher mLauncher;
+    private final AppWidgetManager mAppWidgetManger;
+    private final HashMap<ComponentKey, SearchWidgetInfoContainer> mWidgetPlaceholders =
+            new HashMap<>();
+    private final HashMap<Uri, LiveData<Slice>> mUriSliceMap = new HashMap<>();
+    private SearchWidgetHost mSearchWidgetHost;
+
+    public LiveSearchManager(Launcher launcher) {
+        mLauncher = launcher;
+        mAppWidgetManger = AppWidgetManager.getInstance(launcher);
+    }
+
+    /**
+     * Creates new {@link AppWidgetHostView} from {@link AppWidgetProviderInfo}. Caches views for
+     * quicker result within the same search session
+     */
+    public SearchWidgetInfoContainer getPlaceHolderWidget(AppWidgetProviderInfo providerInfo) {
+        if (mSearchWidgetHost == null) {
+            throw new RuntimeException("AppWidgetHost has not been created yet");
+        }
+
+        ComponentName provider = providerInfo.provider;
+        UserHandle userHandle = providerInfo.getProfile();
+
+        ComponentKey key = new ComponentKey(provider, userHandle);
+        SearchWidgetInfoContainer view = mWidgetPlaceholders.getOrDefault(key, null);
+        if (mWidgetPlaceholders.containsKey(key)) {
+            return mWidgetPlaceholders.get(key);
+        }
+        LauncherAppWidgetProviderInfo pinfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
+                mLauncher, providerInfo);
+        PendingAddWidgetInfo pendingAddWidgetInfo = new PendingAddWidgetInfo(pinfo);
+
+        Bundle options = getDefaultOptionsForWidget(mLauncher, pendingAddWidgetInfo);
+        int appWidgetId = mSearchWidgetHost.allocateAppWidgetId();
+        boolean success = mAppWidgetManger.bindAppWidgetIdIfAllowed(appWidgetId, userHandle,
+                provider, options);
+        if (!success) {
+            mWidgetPlaceholders.put(key, null);
+            return null;
+        }
+
+        view = (SearchWidgetInfoContainer) mSearchWidgetHost.createView(mLauncher, appWidgetId,
+                providerInfo);
+        view.setTag(pendingAddWidgetInfo);
+        mWidgetPlaceholders.put(key, view);
+        return view;
+    }
+
+    /**
+     * Creates {@link LiveData<Slice>} from Slice Uri. Caches created live data to be reused
+     * within the same search session. Removes previous observers when new SliceView request a
+     * live data for observation.
+     */
+    public LiveData<Slice> getSliceForUri(Uri sliceUri) {
+        LiveData<Slice> sliceLiveData = mUriSliceMap.getOrDefault(sliceUri, null);
+        if (sliceLiveData == null) {
+            sliceLiveData = SliceLiveData.fromUri(mLauncher, sliceUri);
+            mUriSliceMap.put(sliceUri, sliceLiveData);
+        }
+        return sliceLiveData;
+    }
+
+    /**
+     * Start search session
+     */
+    public void start() {
+        stop();
+        mSearchWidgetHost = new SearchWidgetHost(mLauncher);
+        mSearchWidgetHost.startListening();
+    }
+
+    /**
+     * Stop search session
+     */
+    public void stop() {
+        if (mSearchWidgetHost != null) {
+            mSearchWidgetHost.stopListening();
+            mSearchWidgetHost.deleteHost();
+            for (SearchWidgetInfoContainer placeholder : mWidgetPlaceholders.values()) {
+                placeholder.clearListeners();
+            }
+            mWidgetPlaceholders.clear();
+            mSearchWidgetHost = null;
+        }
+        for (LiveData<Slice> liveData : mUriSliceMap.values()) {
+            liveData.removeObservers(mLauncher);
+        }
+        mUriSliceMap.clear();
+    }
+
+    static class SearchWidgetHost extends AppWidgetHost {
+        SearchWidgetHost(Context context) {
+            super(context, SEARCH_APPWIDGET_HOST_ID);
+        }
+
+        @Override
+        protected AppWidgetHostView onCreateView(Context context, int appWidgetId,
+                AppWidgetProviderInfo appWidget) {
+            return new SearchWidgetInfoContainer(context);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
new file mode 100644
index 0000000..b5c2268
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchWidgetInfoContainer.java
@@ -0,0 +1,79 @@
+/*
+ * 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.launcher3.allapps.search;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A placeholder {@link AppWidgetHostView} used for managing widget search results
+ */
+public class SearchWidgetInfoContainer extends AppWidgetHostView {
+    private int mAppWidgetId;
+    private AppWidgetProviderInfo mProviderInfo;
+    private RemoteViews mViews;
+    private List<AppWidgetHostView> mListeners = new ArrayList<>();
+
+    public SearchWidgetInfoContainer(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
+        mAppWidgetId = appWidgetId;
+        mProviderInfo = info;
+        for (AppWidgetHostView listener : mListeners) {
+            listener.setAppWidget(mAppWidgetId, mProviderInfo);
+        }
+    }
+
+    @Override
+    public void updateAppWidget(RemoteViews remoteViews) {
+        mViews = remoteViews;
+        for (AppWidgetHostView listener : mListeners) {
+            listener.updateAppWidget(remoteViews);
+        }
+    }
+
+    /**
+     * Create a live {@link AppWidgetHostView} from placeholder
+     */
+    public void attachWidget(AppWidgetHostView hv) {
+        hv.setTag(getTag());
+        hv.setAppWidget(mAppWidgetId, mProviderInfo);
+        hv.updateAppWidget(mViews);
+        mListeners.add(hv);
+    }
+
+    /**
+     * stops AppWidgetHostView from getting updates
+     */
+    public void detachWidget(AppWidgetHostView hostView) {
+        mListeners.remove(hostView);
+    }
+
+    /**
+     * Removes all AppWidgetHost update listeners
+     */
+    public void clearListeners() {
+        mListeners.clear();
+    }
+}
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index dcdfb6e..edaf51d 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -29,8 +29,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 
-import androidx.annotation.Nullable;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -72,7 +70,6 @@
     private Runnable mEndAction;
 
     protected boolean mTargetCancelled = false;
-    protected Runnable mOnCancelRunnable;
 
     /** package private */
     AnimatorPlaybackController(AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
@@ -88,16 +85,11 @@
             @Override
             public void onAnimationCancel(Animator animation) {
                 mTargetCancelled = true;
-                if (mOnCancelRunnable != null) {
-                    mOnCancelRunnable.run();
-                    mOnCancelRunnable = null;
-                }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 mTargetCancelled = false;
-                mOnCancelRunnable = null;
             }
 
             @Override
@@ -269,33 +261,6 @@
         }
     }
 
-    /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
-    public void dispatchOnCancelWithoutCancelRunnable() {
-        dispatchOnCancelWithoutCancelRunnable(null);
-    }
-
-    /**
-     * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
-     * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
-     * @param callback An optional callback to run after dispatching the cancel but before resetting
-     *                 the onCancelRunnable.
-     */
-    public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) {
-        Runnable onCancel = mOnCancelRunnable;
-        setOnCancelRunnable(null);
-        dispatchOnCancel();
-        if (callback != null) {
-            callback.run();
-        }
-        setOnCancelRunnable(onCancel);
-    }
-
-
-    public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) {
-        mOnCancelRunnable = runnable;
-        return this;
-    }
-
     public void dispatchOnStart() {
         callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart);
     }
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 6dd316e..4e90c9e 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.anim;
 
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -43,8 +45,6 @@
  */
 public class PendingAnimation implements PropertySetter {
 
-    private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
-
     private final ArrayList<Holder> mAnimHolders = new ArrayList<>();
     private final AnimatorSet mAnim;
     private final long mDuration;
@@ -73,13 +73,6 @@
         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
     }
 
-    public void finish(boolean isSuccess) {
-        for (Consumer<EndState> listeners : mEndListeners) {
-            listeners.accept(new EndState(isSuccess));
-        }
-        mEndListeners.clear();
-    }
-
     @Override
     public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
         if (view == null || view.getAlpha() == alpha) {
@@ -163,19 +156,38 @@
     }
 
     /**
-     * Add a listener of receiving the end state.
-     * Note that the listeners are called as a result of calling {@link #finish(boolean)}
-     * and not automatically
+     * Add a listener of receiving the success/failure callback in the end.
      */
-    public void addEndListener(Consumer<EndState> listener) {
-        mEndListeners.add(listener);
+    public void addEndListener(Consumer<Boolean> listener) {
+        if (mProgressAnimator == null) {
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
+        }
+        mProgressAnimator.addListener(new EndStateCallbackWrapper(listener));
     }
 
-    public static class EndState {
-        public boolean isSuccess;
+    private static class EndStateCallbackWrapper extends AnimatorListenerAdapter {
 
-        public EndState(boolean isSuccess) {
-            this.isSuccess = isSuccess;
+        private final Consumer<Boolean> mListener;
+        private boolean mCalled = false;
+
+        EndStateCallbackWrapper(Consumer<Boolean> listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (!mCalled) {
+                mCalled = true;
+                mListener.accept(false);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCalled) {
+                ValueAnimator anim = (ValueAnimator) animation;
+                mListener.accept(anim.getAnimatedFraction() > SUCCESS_TRANSITION_PROGRESS);
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4beaf6e..8e6c2a7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -185,6 +185,10 @@
             "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..0b445bc 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+.
  *
@@ -299,12 +297,6 @@
         @UiEvent(doc = "User tapped on image content in Overview Select mode.")
         LAUNCHER_SELECT_MODE_IMAGE(627),
 
-        @UiEvent(doc = "A folder was replaced by a single item")
-        LAUNCHER_FOLDER_CONVERTED_TO_ICON(628),
-
-        @UiEvent(doc = "A hotseat prediction item was pinned")
-        LAUNCHER_HOTSEAT_PREDICTION_PINNED(629),
-
         @UiEvent(doc = "Activity to add external item was started")
         LAUNCHER_ADD_EXTERNAL_ITEM_START(641),
 
@@ -320,6 +312,12 @@
         @UiEvent(doc = "Item was dragged in external item addition flow")
         LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED(645),
 
+        @UiEvent(doc = "A folder was replaced by a single item")
+        LAUNCHER_FOLDER_CONVERTED_TO_ICON(646),
+
+        @UiEvent(doc = "A hotseat prediction item was pinned")
+        LAUNCHER_HOTSEAT_PREDICTION_PINNED(647),
+
         @UiEvent(doc = "Undo event was tapped.")
         LAUNCHER_UNDO(648),
 
@@ -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..80a684d 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
@@ -744,7 +745,11 @@
                                             + "span=" + appWidgetInfo.spanX + "x"
                                             + appWidgetInfo.spanY + " minSpan="
                                             + widgetProviderInfo.minSpanX + "x"
-                                            + widgetProviderInfo.minSpanY);
+                                            + widgetProviderInfo.minSpanY
+                                            + ", appWidgetInfo.provider="
+                                            + appWidgetInfo.providerName.toShortString()
+                                            + ", widgetProviderInfo.provider="
+                                            + widgetProviderInfo.provider.toShortString());
                                     continue;
                                 }
                                 if (!c.isOnWorkspaceOrHotseat()) {
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/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 6d92b8b..59930ff 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -600,6 +600,17 @@
     }
 
     /**
+     * Dismisses the popup if it is no longer valid
+     */
+    public static void dismissInvalidPopup(BaseDraggingActivity activity) {
+        PopupContainerWithArrow popup = getOpen(activity);
+        if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
+                || !canShow(popup.mOriginalIcon, (ItemInfo) popup.mOriginalIcon.getTag()))) {
+            popup.animateClose();
+        }
+    }
+
+    /**
      * Handler to control drag-and-drop for popup items
      */
     public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 2b04365..2adf8ce 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
@@ -296,6 +297,7 @@
     @Override
     public void bindAllApplications(AppInfo[] apps, int flags) {
         mAppsView.getAppsStore().setApps(apps, flags);
+        PopupContainerWithArrow.dismissInvalidPopup(this);
     }
 
     public PopupDataProvider getPopupDataProvider() {
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
index eb68592..ad3f8df 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutTextView.java
@@ -132,6 +132,12 @@
         mLoadingStatePlaceholder.draw(canvas);
     }
 
+    @Override
+    protected void drawDotIfNecessary(Canvas canvas) {
+        // This view (with the text label to the side of the icon) is not designed for a dot to be
+        // drawn on top of it, so never draw one even if a notification for this shortcut exists.
+    }
+
     private void showLoadingState(boolean loading) {
         if (loading == mShowLoadingState) {
             return;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 9fd53e2..a9d0e61 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.touch;
 
-import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -32,6 +33,7 @@
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -48,7 +50,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
@@ -64,9 +65,6 @@
 public abstract class AbstractStateChangeTouchController
         implements TouchController, SingleAxisSwipeDetector.Listener {
 
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
     /**
      * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
      */
@@ -77,6 +75,9 @@
     protected final SingleAxisSwipeDetector mDetector;
     protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
 
+    protected final AnimatorListener mClearStateOnCancelListener =
+            newCancelListener(this::clearState);
+
     private boolean mNoIntercept;
     private boolean mIsLogContainerSet;
     protected int mStartContainerType;
@@ -85,7 +86,7 @@
     protected LauncherState mFromState;
     protected LauncherState mToState;
     protected AnimatorPlaybackController mCurrentAnimation;
-    protected PendingAnimation mPendingAnimation;
+    protected boolean mGoingBetweenStates = true;
 
     private float mStartProgress;
     // Ratio of transition process [0, 1] to drag displacement (px)
@@ -209,7 +210,7 @@
         mStartProgress = 0;
         mPassedOverviewAtomicThreshold = false;
         if (mCurrentAnimation != null) {
-            mCurrentAnimation.setOnCancelRunnable(null);
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
         }
         int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
                 ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
@@ -237,7 +238,7 @@
             LauncherState toState) {
         return (fromState == NORMAL || fromState == OVERVIEW)
                 && (toState == NORMAL || toState == OVERVIEW)
-                && mPendingAnimation == null;
+                && mGoingBetweenStates;
     }
 
     @Override
@@ -266,7 +267,7 @@
         if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && BuildCompat.isAtLeastR()
                 && (mToState == ALL_APPS || mToState == NORMAL)) {
             mLauncher.getAllAppsController().getInsetController().onDragStart(
-                    mToState == ALL_APPS ? 0 : 1);
+                    mFromState == NORMAL ? 1f : 0f);
         }
     }
 
@@ -412,9 +413,8 @@
                             ? mToState : mFromState;
             // snap to top or bottom using the release velocity
         } else {
-            float successProgress = mToState == ALL_APPS
-                    ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
-            targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
+            targetState =
+                    (interpolatedProgress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
         final float endProgress;
@@ -438,7 +438,8 @@
         } else {
             // Let the state manager know that the animation didn't go to the target state,
             // but don't cancel ourselves (we already clean up when the animation completes).
-            mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+            mCurrentAnimation.dispatchOnCancel();
 
             endProgress = 0;
             if (progress <= 0) {
@@ -522,13 +523,7 @@
             mAtomicComponentsController = null;
         }
         clearState();
-        boolean shouldGoToTargetState = true;
-        if (mPendingAnimation != null) {
-            boolean reachedTarget = mToState == targetState;
-            mPendingAnimation.finish(reachedTarget);
-            mPendingAnimation = null;
-            shouldGoToTargetState = !reachedTarget;
-        }
+        boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
         if (shouldGoToTargetState) {
             goToTargetState(targetState);
         }
@@ -568,6 +563,7 @@
             mAtomicAnim.cancel();
             mAtomicAnim = null;
         }
+        mGoingBetweenStates = true;
         mScheduleResumeAtomicComponent = false;
         mDetector.finishedScrolling();
         mDetector.setDetectableScrollConditions(0, false);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index d56391d..9b9cb0a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -96,6 +96,8 @@
             if (v instanceof PendingAppWidgetHostView) {
                 onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
             }
+        } else if (tag instanceof RemoteActionItemInfo) {
+            onClickRemoteAction(launcher, (RemoteActionItemInfo) tag);
         }
     }
 
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 17f02be..1d7f747 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -42,12 +42,12 @@
 public class LandscapePagedViewHandler implements PagedOrientationHandler {
 
     @Override
-    public int getPrimaryValue(int x, int y) {
+    public <T> T getPrimaryValue(T x, T y) {
         return y;
     }
 
     @Override
-    public int getSecondaryValue(int x, int y) {
+    public <T> T getSecondaryValue(T x, T y) {
         return x;
     }
 
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 114b75a..a9c50cd 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -84,8 +84,8 @@
     boolean getRecentsRtlSetting(Resources resources);
     float getDegreesRotated();
     int getRotation();
-    int getPrimaryValue(int x, int y);
-    int getSecondaryValue(int x, int y);
+    <T> T getPrimaryValue(T x, T y);
+    <T> T getSecondaryValue(T x, T y);
     void delegateScrollTo(PagedView pagedView, int secondaryScroll, int primaryScroll);
     /** Uses {@params pagedView}.getScroll[X|Y]() method for the secondary amount*/
     void delegateScrollTo(PagedView pagedView, int primaryScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 5f5b2d1..587e35a 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -40,12 +40,12 @@
 public class PortraitPagedViewHandler implements PagedOrientationHandler {
 
     @Override
-    public int getPrimaryValue(int x, int y) {
+    public <T> T getPrimaryValue(T x, T y) {
         return x;
     }
 
     @Override
-    public int getSecondaryValue(int x, int y) {
+    public <T> T getSecondaryValue(T x, T y) {
         return y;
     }
 
diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java
index 30c9ff9..ee64e98 100644
--- a/src/com/android/launcher3/util/ContentWriter.java
+++ b/src/com/android/launcher3/util/ContentWriter.java
@@ -113,14 +113,26 @@
 
     public static final class CommitParams {
 
-        final Uri mUri = LauncherSettings.Favorites.CONTENT_URI;
-        String mWhere;
-        String[] mSelectionArgs;
+        final Uri mUri;
+        final String mWhere;
+        final String[] mSelectionArgs;
 
         public CommitParams(String where, String[] selectionArgs) {
+            this(LauncherSettings.Favorites.CONTENT_URI, where, selectionArgs);
+        }
+
+        private CommitParams(Uri uri, String where, String[] selectionArgs) {
+            mUri = uri;
             mWhere = where;
             mSelectionArgs = selectionArgs;
         }
 
+        /**
+         * Creates commit params for backup table.
+         */
+        public static CommitParams backupCommitParams(String where, String[] selectionArgs) {
+            return new CommitParams(
+                    LauncherSettings.Favorites.BACKUP_CONTENT_URI, where, selectionArgs);
+        }
     }
 }
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
deleted file mode 100644
index a098df9..0000000
--- a/src/com/android/launcher3/views/HeroSearchResultView.java
+++ /dev/null
@@ -1,209 +0,0 @@
-/*
- * 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.launcher3.views;
-
-import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Point;
-import android.util.AttributeSet;
-import android.util.Pair;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DragSource;
-import com.android.launcher3.DropTarget;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsStore;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
-import com.android.launcher3.allapps.search.SearchEventTracker;
-import com.android.launcher3.dragndrop.DragOptions;
-import com.android.launcher3.dragndrop.DraggableView;
-import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * A view representing a high confidence app search result that includes shortcuts
- * TODO (sfufa@) consolidate this with SearchResultIconRow
- */
-public class HeroSearchResultView extends LinearLayout implements DragSource, SearchTargetHandler {
-
-    public static final String TARGET_TYPE_HERO_APP = "hero_app";
-
-    public static final int MAX_SHORTCUTS_COUNT = 2;
-
-    private SearchTarget mSearchTarget;
-    private BubbleTextView mBubbleTextView;
-    private View mIconView;
-    private BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
-
-
-    public HeroSearchResultView(Context context) {
-        super(context);
-    }
-
-    public HeroSearchResultView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public HeroSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        Launcher launcher = Launcher.getLauncher(getContext());
-        DeviceProfile grid = launcher.getDeviceProfile();
-        mIconView = findViewById(R.id.icon);
-        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
-        iconParams.height = grid.allAppsIconSizePx;
-        iconParams.width = grid.allAppsIconSizePx;
-
-
-        mBubbleTextView = findViewById(R.id.bubble_text);
-        mBubbleTextView.setOnClickListener(view -> {
-            handleSelection(SearchTargetEvent.SELECT);
-            launcher.getItemOnClickListener().onClick(view);
-        });
-        mBubbleTextView.setOnLongClickListener(new HeroItemDragHandler(getContext(), this));
-
-
-        mDeepShortcutTextViews[0] = findViewById(R.id.shortcut_0);
-        mDeepShortcutTextViews[1] = findViewById(R.id.shortcut_1);
-        for (BubbleTextView bubbleTextView : mDeepShortcutTextViews) {
-            bubbleTextView.setLayoutParams(
-                    new LinearLayout.LayoutParams(grid.allAppsIconSizePx,
-                            grid.allAppsIconSizePx));
-            bubbleTextView.setOnClickListener(view -> {
-                WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
-                SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
-                        SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
-                SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
-                launcher.getItemOnClickListener().onClick(view);
-            });
-        }
-    }
-
-    @Override
-    public void applySearchTarget(SearchTarget searchTarget) {
-        mSearchTarget = searchTarget;
-        AllAppsStore apps = Launcher.getLauncher(getContext()).getAppsView().getAppsStore();
-        AppInfo appInfo = apps.getApp(new ComponentKey(searchTarget.getComponentName(),
-                searchTarget.getUserHandle()));
-        List<ShortcutInfo> infos = mSearchTarget.getShortcutInfos();
-
-        ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcuts = new ArrayList<>();
-        for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) {
-            ShortcutInfo shortcutInfo = infos.get(i);
-            ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext());
-            si.rank = i;
-            shortcuts.add(new Pair<>(shortcutInfo, si));
-        }
-
-        mBubbleTextView.applyFromApplicationInfo(appInfo);
-        mIconView.setBackground(mBubbleTextView.getIcon());
-        mIconView.setTag(appInfo);
-        LauncherAppState appState = LauncherAppState.getInstance(getContext());
-        for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
-            BubbleTextView shortcutView = mDeepShortcutTextViews[i];
-            mDeepShortcutTextViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE);
-            if (i < shortcuts.size()) {
-                Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcuts.get(i);
-                //apply ItemInfo and prepare view
-                shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second);
-                MODEL_EXECUTOR.execute(() -> {
-                    // load unbadged shortcut in background and update view when icon ready
-                    appState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first);
-                    MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second));
-                });
-            }
-        }
-        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
-    }
-
-    @Override
-    public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
-        mBubbleTextView.setVisibility(VISIBLE);
-        mBubbleTextView.setIconVisible(true);
-    }
-
-    private void setWillDrawIcon(boolean willDraw) {
-        mIconView.setVisibility(willDraw ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    /**
-     * Drag and drop handler for popup items in Launcher activity
-     */
-    public static class HeroItemDragHandler implements OnLongClickListener {
-        private final Launcher mLauncher;
-        private final HeroSearchResultView mContainer;
-
-        HeroItemDragHandler(Context context, HeroSearchResultView container) {
-            mLauncher = Launcher.getLauncher(context);
-            mContainer = container;
-        }
-
-        @Override
-        public boolean onLongClick(View v) {
-            if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
-            mContainer.setWillDrawIcon(false);
-
-            DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
-            WorkspaceItemInfo itemInfo = new WorkspaceItemInfo((AppInfo) v.getTag());
-            itemInfo.container = CONTAINER_ALL_APPS;
-            DragPreviewProvider previewProvider = new ShortcutDragPreviewProvider(
-                    mContainer.mIconView, new Point());
-            mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
-                    draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
-
-            SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget,
-                    SearchTargetEvent.LONG_PRESS).build();
-            SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event);
-            return false;
-        }
-    }
-
-    @Override
-    public void handleSelection(int eventType) {
-        ItemInfo itemInfo = (ItemInfo) mBubbleTextView.getTag();
-        if (itemInfo == null) return;
-        Launcher launcher = Launcher.getLauncher(getContext());
-        launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
-
-        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
-    }
-}
diff --git a/src/com/android/launcher3/views/SearchResultIcon.java b/src/com/android/launcher3/views/SearchResultIcon.java
index ea06d5b..51c741b 100644
--- a/src/com/android/launcher3/views/SearchResultIcon.java
+++ b/src/com/android/launcher3/views/SearchResultIcon.java
@@ -15,21 +15,40 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewGroup;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.RemoteActionItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.ComponentKey;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
+import java.util.function.Consumer;
+
 /**
  * A {@link BubbleTextView} representing a single cell result in AllApps
  */
@@ -39,10 +58,21 @@
 
 
     public static final String TARGET_TYPE_APP = "app";
+    public static final String TARGET_TYPE_HERO_APP = "hero_app";
+    public static final String TARGET_TYPE_SHORTCUT = "shortcut";
+    public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action";
+
+    public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
+    public static final String REMOTE_ACTION_TOKEN = "action_token";
+
+
+    private static final String[] LONG_PRESS_SUPPORTED_TYPES =
+            new String[]{TARGET_TYPE_APP, TARGET_TYPE_SHORTCUT, TARGET_TYPE_HERO_APP};
 
     private final Launcher mLauncher;
 
     private SearchTarget mSearchTarget;
+    private Consumer<ItemInfoWithIcon> mOnItemInfoChanged;
 
     public SearchResultIcon(Context context) {
         this(context, null, 0);
@@ -64,26 +94,96 @@
         setOnFocusChangeListener(mLauncher.getFocusHandler());
         setOnClickListener(this);
         setOnLongClickListener(this);
-        getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+        setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                mLauncher.getDeviceProfile().allAppsCellHeightPx));
+    }
+
+    /**
+     * Applies search target with a ItemInfoWithIcon consumer to be called after itemInfo is
+     * constructed
+     */
+    public void applySearchTarget(SearchTarget searchTarget, Consumer<ItemInfoWithIcon> cb) {
+        mOnItemInfoChanged = cb;
+        applySearchTarget(searchTarget);
     }
 
     @Override
     public void applySearchTarget(SearchTarget searchTarget) {
         mSearchTarget = searchTarget;
-        AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
         SearchEventTracker.getInstance(getContext()).registerWeakHandler(mSearchTarget, this);
-        if (searchTarget.getItemType().equals(TARGET_TYPE_APP)) {
-            AppInfo appInfo = appsStore.getApp(new ComponentKey(searchTarget.getComponentName(),
-                    searchTarget.getUserHandle()));
-            applyFromApplicationInfo(appInfo);
+        switch (searchTarget.getItemType()) {
+            case TARGET_TYPE_APP:
+            case TARGET_TYPE_HERO_APP:
+                prepareUsingApp(searchTarget.getComponentName(), searchTarget.getUserHandle());
+                break;
+            case TARGET_TYPE_SHORTCUT:
+                prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
+                break;
+            case TARGET_TYPE_REMOTE_ACTION:
+                prepareUsingRemoteAction(searchTarget.getRemoteAction(),
+                        searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN),
+                        searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START),
+                        searchTarget.getItemType().equals(TARGET_TYPE_REMOTE_ACTION));
+                break;
         }
     }
 
+    private void prepareUsingApp(ComponentName componentName, UserHandle userHandle) {
+        AllAppsStore appsStore = mLauncher.getAppsView().getAppsStore();
+        AppInfo appInfo = appsStore.getApp(new ComponentKey(componentName, userHandle));
+        applyFromApplicationInfo(appInfo);
+        notifyItemInfoChanged(appInfo);
+    }
+
+
+    private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
+        notifyItemInfoChanged(workspaceItemInfo);
+        LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
+        MODEL_EXECUTOR.execute(() -> {
+            launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
+            MAIN_EXECUTOR.post(() -> applyFromWorkspaceItem(workspaceItemInfo));
+        });
+    }
+
+    private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start,
+            boolean useIconToBadge) {
+        RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
+        notifyItemInfoChanged(itemInfo);
+        UI_HELPER_EXECUTOR.post(() -> {
+            // If the Drawable from the remote action is not AdaptiveBitmap, styling will not
+            // work.
+            try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
+                Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
+                BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
+                        Build.VERSION.SDK_INT);
+
+                if (useIconToBadge) {
+                    BitmapInfo placeholder = li.createIconBitmap(
+                            itemInfo.getRemoteAction().getTitle().toString().substring(0, 1),
+                            bitmap.color);
+                    itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap);
+                } else {
+                    itemInfo.bitmap = bitmap;
+                }
+            }
+            MAIN_EXECUTOR.post(() -> applyFromRemoteActionInfo(itemInfo));
+        });
+    }
+
     @Override
     public void handleSelection(int eventType) {
         mLauncher.getItemOnClickListener().onClick(this);
-        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
+        reportEvent(eventType);
+    }
+
+    private void reportEvent(int eventType) {
+        SearchTargetEvent.Builder b = new SearchTargetEvent.Builder(mSearchTarget, eventType);
+        if (mSearchTarget.getItemType().equals(TARGET_TYPE_SHORTCUT)) {
+            b.setShortcutPosition(0);
+        }
+        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(b.build());
+
     }
 
     @Override
@@ -93,8 +193,25 @@
 
     @Override
     public boolean onLongClick(View view) {
-        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build());
+        if (!supportsLongPress(mSearchTarget.getItemType())) {
+            return false;
+        }
+        reportEvent(SearchTargetEvent.LONG_PRESS);
         return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
+
+    }
+
+    private boolean supportsLongPress(String type) {
+        for (String t : LONG_PRESS_SUPPORTED_TYPES) {
+            if (t.equals(type)) return true;
+        }
+        return false;
+    }
+
+    private void notifyItemInfoChanged(ItemInfoWithIcon itemInfoWithIcon) {
+        if (mOnItemInfoChanged != null) {
+            mOnItemInfoChanged.accept(itemInfoWithIcon);
+            mOnItemInfoChanged = null;
+        }
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java
index fe904ff..e3c7661 100644
--- a/src/com/android/launcher3/views/SearchResultIconRow.java
+++ b/src/com/android/launcher3/views/SearchResultIconRow.java
@@ -17,180 +17,185 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
-import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ShortcutInfo;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.AttributeSet;
-import android.widget.EditText;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.allapps.search.SearchEventTracker;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.RemoteActionItemInfo;
+import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.Themes;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 /**
- * A view representing a stand alone shortcut search result
+ * A full width representation of {@link SearchResultIcon} with a secondary label and inline
+ * shortcuts
  */
-public class SearchResultIconRow extends DoubleShadowBubbleTextView implements
-        AllAppsSearchBarController.SearchTargetHandler {
+public class SearchResultIconRow extends LinearLayout implements
+        AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener,
+        View.OnLongClickListener, Consumer<ItemInfoWithIcon> {
+    public static final int MAX_SHORTCUTS_COUNT = 2;
 
 
-    public static final String TARGET_TYPE_REMOTE_ACTION = "remote_action";
-    public static final String TARGET_TYPE_SUGGEST = "suggest";
-    public static final String TARGET_TYPE_SHORTCUT = "shortcut";
-
-
-    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 final Launcher mLauncher;
+    private final LauncherAppState mLauncherAppState;
+    private SearchResultIcon mResultIcon;
+    private TextView mTitleView;
+    private TextView mDescriptionView;
+    private BubbleTextView[] mShortcutViews = new BubbleTextView[2];
 
     private SearchTarget mSearchTarget;
+    private PackageItemInfo mProviderInfo;
 
 
-    public SearchResultIconRow(@NonNull Context context) {
+    public SearchResultIconRow(Context context) {
         this(context, null, 0);
     }
 
-    public SearchResultIconRow(@NonNull Context context,
+    public SearchResultIconRow(Context context,
             @Nullable AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
-    public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
+    public SearchResultIconRow(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         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);
-
-        a.recycle();
+        mLauncher = Launcher.getLauncher(getContext());
+        mLauncherAppState = LauncherAppState.getInstance(getContext());
     }
 
-
     @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        Launcher launcher = Launcher.getLauncher(getContext());
-        if (mMatchesInset && launcher.getAppsView() != null && getParent() != null) {
-            EditText editText = launcher.getAppsView().getSearchUiManager().getEditText();
-            if (editText != null) {
-                int counterOffset = getIconSize() + getCompoundDrawablePadding() / 2;
-                setPadding(editText.getLeft() - counterOffset, getPaddingTop(),
-                        getPaddingRight(), getPaddingBottom());
-            }
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        int iconSize = mLauncher.getDeviceProfile().allAppsIconSizePx;
+
+        mResultIcon = findViewById(R.id.icon);
+        mTitleView = findViewById(R.id.title);
+        mDescriptionView = findViewById(R.id.desc);
+        mShortcutViews[0] = findViewById(R.id.shortcut_0);
+        mShortcutViews[1] = findViewById(R.id.shortcut_1);
+        mResultIcon.getLayoutParams().height = iconSize;
+        mResultIcon.getLayoutParams().width = iconSize;
+        mResultIcon.setTextVisibility(false);
+        for (BubbleTextView bubbleTextView : mShortcutViews) {
+            ViewGroup.LayoutParams lp = bubbleTextView.getLayoutParams();
+            lp.width = iconSize;
+            bubbleTextView.setOnClickListener(view -> {
+                WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
+                SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
+                        SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
+                SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
+                mLauncher.getItemOnClickListener().onClick(view);
+            });
         }
+        setOnClickListener(this);
+        setOnLongClickListener(this);
     }
 
     @Override
-    protected void drawFocusHighlight(Canvas canvas) {
-        mHighlightPaint.setColor(mHighlightColor);
-        float r = Themes.getDialogCornerRadius(getContext());
-        canvas.drawRoundRect(0, 0, getWidth(), getHeight(), r, r, mHighlightPaint);
-    }
-
-
-    @Override
     public void applySearchTarget(SearchTarget searchTarget) {
         mSearchTarget = searchTarget;
-        String type = searchTarget.getItemType();
-        if (type.equals(TARGET_TYPE_REMOTE_ACTION) || type.equals(TARGET_TYPE_SUGGEST)) {
-            prepareUsingRemoteAction(searchTarget.getRemoteAction(),
-                    searchTarget.getExtras().getString(REMOTE_ACTION_TOKEN),
-                    searchTarget.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START),
-                    type.equals(TARGET_TYPE_REMOTE_ACTION));
+        mResultIcon.applySearchTarget(searchTarget, this);
+        String itemType = searchTarget.getItemType();
+        boolean showDesc = itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT);
+        mDescriptionView.setVisibility(showDesc ? VISIBLE : GONE);
 
-        } else if (type.equals(TARGET_TYPE_SHORTCUT)) {
-            prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
+        if (itemType.equals(SearchResultIcon.TARGET_TYPE_SHORTCUT)) {
+            ShortcutInfo shortcutInfo = searchTarget.getShortcutInfos().get(0);
+            setProviderDetails(new ComponentName(shortcutInfo.getPackage(), ""),
+                    shortcutInfo.getUserHandle());
+        } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) {
+            showInlineShortcuts(mSearchTarget.getShortcutInfos());
+        } else if (itemType.equals(SearchResultIcon.TARGET_TYPE_REMOTE_ACTION)) {
+            CharSequence desc = mSearchTarget.getRemoteAction().getContentDescription();
+            if (!TextUtils.isEmpty(desc)) {
+                mDescriptionView.setVisibility(VISIBLE);
+                mDescriptionView.setText(desc);
+            }
         }
-        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
-        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
+        if (!itemType.equals(SearchResultIcon.TARGET_TYPE_HERO_APP)) {
+            showInlineShortcuts(new ArrayList<>());
+        }
     }
 
-    private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
-        applyFromWorkspaceItem(workspaceItemInfo);
-        LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
-        if (!loadIconFromResource()) {
-            MODEL_EXECUTOR.execute(() -> {
-                launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
-                reapplyItemInfoAsync(workspaceItemInfo);
+    @Override
+    public void accept(ItemInfoWithIcon itemInfoWithIcon) {
+        mTitleView.setText(itemInfoWithIcon.title);
+    }
+
+    private void showInlineShortcuts(List<ShortcutInfo> infos) {
+        if (infos == null) return;
+        ArrayList<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcuts = new ArrayList<>();
+        for (int i = 0; infos != null && i < infos.size() && i < MAX_SHORTCUTS_COUNT; i++) {
+            ShortcutInfo shortcutInfo = infos.get(i);
+            ItemInfoWithIcon si = new WorkspaceItemInfo(shortcutInfo, getContext());
+            si.rank = i;
+            shortcuts.add(new Pair<>(shortcutInfo, si));
+        }
+
+        for (int i = 0; i < mShortcutViews.length; i++) {
+            BubbleTextView shortcutView = mShortcutViews[i];
+            mShortcutViews[i].setVisibility(shortcuts.size() > i ? VISIBLE : GONE);
+            if (i < shortcuts.size()) {
+                Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcuts.get(i);
+                //apply ItemInfo and prepare view
+                shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second);
+                MODEL_EXECUTOR.execute(() -> {
+                    // load unbadged shortcut in background and update view when icon ready
+                    mLauncherAppState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first);
+                    MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second));
+                });
+            }
+        }
+    }
+
+
+    private void setProviderDetails(ComponentName componentName, UserHandle userHandle) {
+        PackageItemInfo packageItemInfo = new PackageItemInfo(componentName.getPackageName());
+        if (mProviderInfo == packageItemInfo) return;
+        MODEL_EXECUTOR.post(() -> {
+            packageItemInfo.user = userHandle;
+            mLauncherAppState.getIconCache().getTitleAndIconForApp(packageItemInfo, true);
+            MAIN_EXECUTOR.post(() -> {
+                mDescriptionView.setText(packageItemInfo.title);
+                mProviderInfo = packageItemInfo;
             });
-        }
-    }
-
-    private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start,
-            boolean useIconToBadge) {
-        RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
-
-        applyFromRemoteActionInfo(itemInfo);
-        if (itemInfo.isEscapeHatch() || !loadIconFromResource()) {
-            UI_HELPER_EXECUTOR.post(() -> {
-                // If the Drawable from the remote action is not AdaptiveBitmap, styling will not
-                // work.
-                try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
-                    Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
-                    BitmapInfo bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
-                            Build.VERSION.SDK_INT);
-
-                    if (useIconToBadge) {
-                        BitmapInfo placeholder = li.createIconBitmap(
-                                itemInfo.getRemoteAction().getTitle().toString().substring(0, 1),
-                                bitmap.color);
-                        itemInfo.bitmap = li.badgeBitmap(placeholder.icon, bitmap);
-                    } else {
-                        itemInfo.bitmap = bitmap;
-                    }
-                    reapplyItemInfoAsync(itemInfo);
-                }
-            });
-        }
-
-    }
-
-    private boolean loadIconFromResource() {
-        if (mCustomIconResId == 0) return false;
-        setIcon(Launcher.getLauncher(getContext()).getDrawable(mCustomIconResId));
-        return true;
-    }
-
-    void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
-        MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon));
+        });
     }
 
     @Override
     public void handleSelection(int eventType) {
-        ItemInfo itemInfo = (ItemInfo) getTag();
-        Launcher launcher = Launcher.getLauncher(getContext());
-        if (itemInfo instanceof WorkspaceItemInfo) {
-            ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
-        } else {
-            ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo);
-        }
-        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
+        mResultIcon.handleSelection(eventType);
+    }
+
+    @Override
+    public void onClick(View view) {
+        mResultIcon.performClick();
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        mResultIcon.performLongClick();
+        return false;
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultSettingsSlice.java b/src/com/android/launcher3/views/SearchResultSettingsSlice.java
new file mode 100644
index 0000000..29e6c1b
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultSettingsSlice.java
@@ -0,0 +1,128 @@
+/*
+ * 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.launcher3.views;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.slice.Slice;
+import androidx.slice.SliceItem;
+import androidx.slice.widget.EventInfo;
+import androidx.slice.widget.SliceView;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * A slice view wrapper with settings app icon at start
+ */
+public class SearchResultSettingsSlice extends LinearLayout implements
+        AllAppsSearchBarController.SearchTargetHandler, SliceView.OnSliceActionListener {
+
+
+    public static final String TARGET_TYPE_SLICE = "settings_slice";
+
+    private static final String TAG = "SearchSliceController";
+    private static final String URI_EXTRA_KEY = "slice_uri";
+
+    private SliceView mSliceView;
+    private View mIcon;
+    private LiveData<Slice> mSliceLiveData;
+    private SearchTarget mSearchTarget;
+    private final Launcher mLauncher;
+
+    public SearchResultSettingsSlice(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultSettingsSlice(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultSettingsSlice(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(getContext());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mSliceView = findViewById(R.id.slice);
+        mIcon = findViewById(R.id.icon);
+        SearchSettingsRowView.applySettingsIcon(mLauncher, mIcon);
+    }
+
+    @Override
+    public void applySearchTarget(SearchTarget searchTarget) {
+        reset();
+        mSearchTarget = searchTarget;
+        try {
+            mSliceLiveData = mLauncher.getLiveSearchManager().getSliceForUri(getSliceUri());
+            mSliceLiveData.observe(mLauncher, mSliceView);
+        } catch (Exception ex) {
+            Log.e(TAG, "unable to bind slice", ex);
+        }
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mSliceView.setOnSliceActionListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        reset();
+    }
+
+    @Override
+    public void handleSelection(int eventType) {
+        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget,
+                        SearchTargetEvent.CHILD_SELECT).build());
+    }
+
+    private void reset() {
+        mSliceView.setOnSliceActionListener(null);
+        if (mSliceLiveData != null) {
+            mSliceLiveData.removeObservers(mLauncher);
+        }
+    }
+
+    @Override
+    public void onSliceAction(@NonNull EventInfo eventInfo, @NonNull SliceItem sliceItem) {
+        handleSelection(SearchTargetEvent.CHILD_SELECT);
+    }
+
+    private Uri getSliceUri() {
+        return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
+    }
+
+}
diff --git a/src/com/android/launcher3/views/SearchResultSuggestion.java b/src/com/android/launcher3/views/SearchResultSuggestion.java
new file mode 100644
index 0000000..c67b1cf
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultSuggestion.java
@@ -0,0 +1,62 @@
+/*
+ * 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.launcher3.views;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+
+import com.android.launcher3.R;
+
+/**
+ * {@link SearchResultIconRow} with custom drawable resource
+ */
+public class SearchResultSuggestion extends SearchResultIcon {
+
+    public static final String TARGET_TYPE_SUGGEST = "suggest";
+    private final Drawable mCustomIcon;
+
+    public SearchResultSuggestion(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultSuggestion(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultSuggestion(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.SearchResultSuggestion, defStyle, 0);
+        mCustomIcon = a.getDrawable(R.styleable.SearchResultSuggestion_customIcon);
+        a.recycle();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        ViewGroup.LayoutParams lp = getLayoutParams();
+        lp.height = BaseDragLayer.LayoutParams.WRAP_CONTENT;
+    }
+
+    @Override
+    protected void setIcon(Drawable icon) {
+        super.setIcon(mCustomIcon);
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultWidget.java b/src/com/android/launcher3/views/SearchResultWidget.java
new file mode 100644
index 0000000..f76de3d
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultWidget.java
@@ -0,0 +1,204 @@
+/*
+ * 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.launcher3.views;
+
+import android.appwidget.AppWidgetHostView;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.RelativeLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.CheckLongPressHelper;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.allapps.search.SearchWidgetInfoContainer;
+import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.widget.PendingAddWidgetInfo;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * displays live version of a widget upon receiving {@link AppWidgetProviderInfo} from Search
+ * provider
+ */
+public class SearchResultWidget extends RelativeLayout implements
+        AllAppsSearchBarController.SearchTargetHandler, DraggableView, View.OnLongClickListener {
+
+    private static final String TAG = "SearchResultWidget";
+
+    public static final String TARGET_TYPE_WIDGET_LIVE = "widget";
+
+    private final Rect mWidgetOffset = new Rect();
+
+    private final Launcher mLauncher;
+    private final CheckLongPressHelper mLongPressHelper;
+    private final GestureDetector mClickDetector;
+    private final AppWidgetHostView mHostView;
+    private final float mScaleToFit;
+
+    private SearchTarget mSearchTarget;
+    private AppWidgetProviderInfo mProviderInfo;
+
+    private SearchWidgetInfoContainer mInfoContainer;
+
+    public SearchResultWidget(@NonNull Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultWidget(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultWidget(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mHostView = new AppWidgetHostView(context);
+        DeviceProfile grid = mLauncher.getDeviceProfile();
+        mScaleToFit = Math.min(grid.appWidgetScale.x, grid.appWidgetScale.y);
+
+        // detect tap event on widget container for search target event reporting
+        mClickDetector = new GestureDetector(context,
+                new ClickListener(() -> handleSelection(SearchTargetEvent.CHILD_SELECT)));
+
+        mLongPressHelper = new CheckLongPressHelper(this);
+        mLongPressHelper.setLongPressTimeoutFactor(1);
+        setOnLongClickListener(this);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        addView(mHostView);
+    }
+
+    @Override
+    public void applySearchTarget(SearchTarget searchTarget) {
+        if (searchTarget.getExtras() == null
+                || searchTarget.getExtras().getParcelable("provider") == null) {
+            setVisibility(GONE);
+            return;
+        }
+        AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider");
+        if (mProviderInfo != null && providerInfo.provider.equals(mProviderInfo.provider)
+                && providerInfo.getProfile().equals(mProviderInfo.getProfile())) {
+            return;
+        }
+        removeListener();
+
+        mSearchTarget = searchTarget;
+        mProviderInfo = providerInfo;
+
+        mInfoContainer = mLauncher.getLiveSearchManager().getPlaceHolderWidget(providerInfo);
+        if (mInfoContainer == null) {
+            setVisibility(GONE);
+            return;
+        }
+        setVisibility(VISIBLE);
+        mInfoContainer.attachWidget(mHostView);
+        PendingAddWidgetInfo info = (PendingAddWidgetInfo) mHostView.getTag();
+        int[] size = mLauncher.getWorkspace().estimateItemSize(info);
+        mHostView.getLayoutParams().width = size[0];
+        mHostView.getLayoutParams().height = size[1];
+        AppWidgetResizeFrame.updateWidgetSizeRanges(mHostView, mLauncher, info.spanX,
+                info.spanY);
+        mHostView.requestLayout();
+        setTag(info);
+    }
+
+    /**
+     * Stops hostView from getting updates on a widget provider
+     */
+    public void removeListener() {
+        if (mInfoContainer != null) {
+            mInfoContainer.detachWidget(mHostView);
+        }
+    }
+
+    @Override
+    public void handleSelection(int eventType) {
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        mLongPressHelper.onTouchEvent(ev);
+        mClickDetector.onTouchEvent(ev);
+        return mLongPressHelper.hasPerformedLongPress();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mLongPressHelper.onTouchEvent(ev);
+        return true;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+        mLongPressHelper.cancelLongPress();
+    }
+
+    @Override
+    public int getViewType() {
+        return DraggableView.DRAGGABLE_WIDGET;
+    }
+
+    @Override
+    public void getSourceVisualDragBounds(Rect bounds) {
+        mHostView.getHitRect(mWidgetOffset);
+        int width = (int) (mHostView.getMeasuredWidth() * mScaleToFit);
+        int height = (int) (mHostView.getMeasuredHeight() * mScaleToFit);
+        bounds.set(mWidgetOffset.left,
+                mWidgetOffset.top,
+                width + mWidgetOffset.left,
+                height + mWidgetOffset.top);
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
+        handleSelection(SearchTargetEvent.LONG_PRESS);
+        return false;
+    }
+
+    static class ClickListener extends GestureDetector.SimpleOnGestureListener {
+        private final Runnable mCb;
+
+        ClickListener(Runnable cb) {
+            mCb = cb;
+        }
+
+        @Override
+        public boolean onSingleTapConfirmed(MotionEvent e) {
+            mCb.run();
+            return super.onSingleTapConfirmed(e);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultWidgetPreview.java b/src/com/android/launcher3/views/SearchResultWidgetPreview.java
new file mode 100644
index 0000000..c11c232
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultWidgetPreview.java
@@ -0,0 +1,141 @@
+/*
+ * 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.launcher3.views;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.Context;
+import android.graphics.Point;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.model.WidgetItem;
+import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.widget.BaseWidgetSheet;
+import com.android.launcher3.widget.PendingItemDragHelper;
+import com.android.launcher3.widget.WidgetCell;
+import com.android.launcher3.widget.WidgetImageView;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * displays preview of a widget upon receiving {@link AppWidgetProviderInfo} from Search provider
+ */
+public class SearchResultWidgetPreview extends LinearLayout implements
+        AllAppsSearchBarController.SearchTargetHandler, View.OnLongClickListener,
+        View.OnClickListener {
+
+    public static final String TARGET_TYPE_WIDGET_PREVIEW = "widget_preview";
+    private final Launcher mLauncher;
+    private final LauncherAppState mAppState;
+    private WidgetCell mWidgetCell;
+    private Toast mWidgetToast;
+
+    private SearchTarget mSearchTarget;
+
+
+    public SearchResultWidgetPreview(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultWidgetPreview(Context context,
+            @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultWidgetPreview(Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mAppState = LauncherAppState.getInstance(context);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mWidgetCell = findViewById(R.id.widget_cell);
+        mWidgetCell.setOnLongClickListener(this);
+        mWidgetCell.setOnClickListener(this);
+    }
+
+    @Override
+    public void applySearchTarget(SearchTarget searchTarget) {
+        if (searchTarget.getExtras() == null
+                || searchTarget.getExtras().getParcelable("provider") == null) {
+            setVisibility(GONE);
+            return;
+        }
+        mSearchTarget = searchTarget;
+        AppWidgetProviderInfo providerInfo = searchTarget.getExtras().getParcelable("provider");
+        LauncherAppWidgetProviderInfo pInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(
+                getContext(), providerInfo);
+        MODEL_EXECUTOR.post(() -> {
+            WidgetItem widgetItem = new WidgetItem(pInfo, mLauncher.getDeviceProfile().inv,
+                    mAppState.getIconCache());
+            MAIN_EXECUTOR.post(() -> {
+                mWidgetCell.applyFromCellItem(widgetItem, mAppState.getWidgetCache());
+                mWidgetCell.ensurePreview();
+            });
+        });
+
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        view.cancelLongPress();
+        if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+        if (mWidgetCell.getTag() == null) return false;
+
+        WidgetImageView imageView = mWidgetCell.getWidgetView();
+        if (imageView.getBitmap() == null) {
+            return false;
+        }
+
+        int[] loc = new int[2];
+        mLauncher.getDragLayer().getLocationInDragLayer(imageView, loc);
+
+        new PendingItemDragHelper(mWidgetCell).startDrag(
+                imageView.getBitmapBounds(), imageView.getBitmap().getWidth(), imageView.getWidth(),
+                new Point(loc[0], loc[1]), mLauncher.getAppsView(), new DragOptions());
+        handleSelection(SearchTargetEvent.LONG_PRESS);
+        return true;
+    }
+
+    @Override
+    public void onClick(View view) {
+        mWidgetToast = BaseWidgetSheet.showWidgetToast(getContext(), mWidgetToast);
+        handleSelection(SearchTargetEvent.SELECT);
+    }
+
+    @Override
+    public void handleSelection(int eventType) {
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
index 30f686c..160ee65 100644
--- a/src/com/android/launcher3/views/SearchSettingsRowView.java
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -15,8 +15,14 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
@@ -27,38 +33,41 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
- * A row of tappable TextViews with a breadcrumb for settings search.
+ * A row of clickable TextViews with a breadcrumb for settings search.
  */
 public class SearchSettingsRowView extends LinearLayout implements
         View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
 
     public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
 
-
+    private View mIconView;
     private TextView mTitleView;
-    private TextView mDescriptionView;
     private TextView mBreadcrumbsView;
     private Intent mIntent;
     private SearchTarget mSearchTarget;
 
 
     public SearchSettingsRowView(@NonNull Context context) {
-        super(context);
+        this(context, null, 0);
     }
 
     public SearchSettingsRowView(@NonNull Context context,
             @Nullable AttributeSet attrs) {
-        super(context, attrs);
+        this(context, attrs, 0);
     }
 
     public SearchSettingsRowView(@NonNull Context context, @Nullable AttributeSet attrs,
@@ -69,10 +78,11 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mIconView = findViewById(R.id.icon);
         mTitleView = findViewById(R.id.title);
-        mDescriptionView = findViewById(R.id.description);
         mBreadcrumbsView = findViewById(R.id.breadcrumbs);
         setOnClickListener(this);
+        applySettingsIcon(Launcher.getLauncher(getContext()), mIconView);
     }
 
     @Override
@@ -81,7 +91,7 @@
         Bundle bundle = searchTarget.getExtras();
         mIntent = bundle.getParcelable("intent");
         showIfAvailable(mTitleView, bundle.getString("title"));
-        showIfAvailable(mDescriptionView, bundle.getString("description"));
+        mIconView.setContentDescription(bundle.getString("title"));
         ArrayList<String> breadcrumbs = bundle.getStringArrayList("breadcrumbs");
         //TODO: implement RTL friendly breadcrumbs view
         showIfAvailable(mBreadcrumbsView, breadcrumbs != null
@@ -114,4 +124,30 @@
         SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
                 new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
+
+    /**
+     * Requests settings app icon from {@link com.android.launcher3.icons.IconCache} and applies
+     * to to view
+     */
+    public static void applySettingsIcon(Launcher launcher, View view) {
+        LauncherAppState appState = LauncherAppState.getInstance(launcher);
+        MODEL_EXECUTOR.post(() -> {
+            PackageItemInfo packageItemInfo = new PackageItemInfo(getSettingsPackageName(launcher));
+            appState.getIconCache().getTitleAndIconForApp(packageItemInfo, false);
+            MAIN_EXECUTOR.post(() -> {
+                FastBitmapDrawable iconDrawable = newIcon(appState.getContext(), packageItemInfo);
+                view.setBackground(iconDrawable);
+            });
+        });
+    }
+
+    private static String getSettingsPackageName(Launcher launcher) {
+        Intent intent = new Intent(android.provider.Settings.ACTION_SETTINGS);
+        List<ResolveInfo> resolveInfos = launcher.getPackageManager().queryIntentActivities(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        if (resolveInfos.size() == 0) {
+            return "";
+        }
+        return resolveInfos.get(0).activityInfo.packageName;
+    }
 }
diff --git a/src/com/android/launcher3/views/SearchSliceWrapper.java b/src/com/android/launcher3/views/SearchSliceWrapper.java
deleted file mode 100644
index f8a7dc0..0000000
--- a/src/com/android/launcher3/views/SearchSliceWrapper.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.launcher3.views;
-
-import android.content.Context;
-import android.net.Uri;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LiveData;
-import androidx.slice.Slice;
-import androidx.slice.SliceItem;
-import androidx.slice.widget.EventInfo;
-import androidx.slice.widget.SliceLiveData;
-import androidx.slice.widget.SliceView;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.allapps.search.SearchEventTracker;
-import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
-
-/**
- * A Wrapper class for {@link SliceView} search results
- */
-public class SearchSliceWrapper implements SliceView.OnSliceActionListener {
-
-    public static final String TARGET_TYPE_SLICE = "settings_slice";
-
-    private static final String TAG = "SearchSliceController";
-    private static final String URI_EXTRA_KEY = "slice_uri";
-
-
-    private final Launcher mLauncher;
-    private final SearchTarget mSearchTarget;
-    private final SliceView mSliceView;
-    private LiveData<Slice> mSliceLiveData;
-
-    public SearchSliceWrapper(Context context, SliceView sliceView, SearchTarget searchTarget) {
-        mLauncher = Launcher.getLauncher(context);
-        mSearchTarget = searchTarget;
-        mSliceView = sliceView;
-        sliceView.setOnSliceActionListener(this);
-        try {
-            mSliceLiveData = SliceLiveData.fromUri(mLauncher, getSliceUri());
-            mSliceLiveData.observe((Launcher) mLauncher, sliceView);
-        } catch (Exception ex) {
-            Log.e(TAG, "unable to bind slice", ex);
-        }
-    }
-
-    /**
-     * Unregisters event handlers and removes lifecycle observer
-     */
-    public void destroy() {
-        mSliceView.setOnSliceActionListener(null);
-        mSliceLiveData.removeObservers(mLauncher);
-    }
-
-    @Override
-    public void onSliceAction(@NonNull EventInfo info, @NonNull SliceItem item) {
-        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
-                new SearchTargetEvent.Builder(mSearchTarget,
-                        SearchTargetEvent.CHILD_SELECT).build());
-    }
-
-    private Uri getSliceUri() {
-        return mSearchTarget.getExtras().getParcelable(URI_EXTRA_KEY);
-    }
-}
diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
index d11b1ef..f213f22 100644
--- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java
+++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
@@ -15,8 +15,9 @@
  */
 package com.android.launcher3.views;
 
-import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_SHOULD_START;
-import static com.android.launcher3.views.SearchResultIconRow.REMOTE_ACTION_TOKEN;
+
+import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_SHOULD_START;
+import static com.android.launcher3.views.SearchResultIcon.REMOTE_ACTION_TOKEN;
 
 import android.content.Context;
 import android.content.Intent;
@@ -90,6 +91,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();
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 01af96c..a38e90d 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -42,7 +42,7 @@
 /**
  * Base class for various widgets popup
  */
-abstract class BaseWidgetSheet extends AbstractSlideInView
+public abstract class BaseWidgetSheet extends AbstractSlideInView
         implements OnClickListener, OnLongClickListener, DragSource,
         PopupDataProvider.PopupDataChangeListener {
 
@@ -74,16 +74,7 @@
 
     @Override
     public final void onClick(View v) {
-        // Let the user know that they have to long press to add a widget
-        if (mWidgetInstructionToast != null) {
-            mWidgetInstructionToast.cancel();
-        }
-
-        CharSequence msg = Utilities.wrapForTts(
-                getContext().getText(R.string.long_press_widget_to_add),
-                getContext().getString(R.string.long_accessible_way_to_add));
-        mWidgetInstructionToast = Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT);
-        mWidgetInstructionToast.show();
+        mWidgetInstructionToast = showWidgetToast(getContext(), mWidgetInstructionToast);
     }
 
     @Override
@@ -146,4 +137,21 @@
     protected SystemUiController getSystemUiController() {
         return mLauncher.getSystemUiController();
     }
+
+    /**
+     * Show Widget tap toast prompting user to drag instead
+     */
+    public static Toast showWidgetToast(Context context, Toast toast) {
+        // Let the user know that they have to long press to add a widget
+        if (toast != null) {
+            toast.cancel();
+        }
+
+        CharSequence msg = Utilities.wrapForTts(
+                context.getText(R.string.long_press_widget_to_add),
+                context.getString(R.string.long_accessible_way_to_add));
+        toast = Toast.makeText(context, msg, Toast.LENGTH_SHORT);
+        toast.show();
+        return toast;
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 8d594de..df0770d 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -65,12 +65,13 @@
 
         String[] tokens = output.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
-
+        Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Created new user uid" + mProfileUserId);
         mDevice.executeShellCommand("am start-user " + mProfileUserId);
     }
 
     @After
     public void removeWorkProfile() throws Exception {
+        Log.d(TestProtocol.WORK_PROFILE_REMOVED, "(teardown) removing uid" + mProfileUserId);
         mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
     }