Merge "Use event time of app side for latency metrics" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 97e3786..a47a500 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -30,6 +30,7 @@
     with some minor changed based on the derivative app.
     -->
 
+    <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
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/TaskViewSimulatorTest.java b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index 5491daa..6c88e55 100644
--- a/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/robolectric_tests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -144,7 +144,7 @@
                     LauncherActivityInterface.INSTANCE);
             tvs.setDp(mDeviceProfile);
 
-            int launcherRotation = DisplayController.INSTANCE.get(mContext).getInfo().rotation;
+            int launcherRotation = DisplayController.getDefaultDisplay(mContext).getInfo().rotation;
             if (mAppRotation < 0) {
                 mAppRotation = launcherRotation;
             }
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index df1833d..54a753c 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -802,33 +802,31 @@
     }
 
     private void addCujInstrumentation(Animator anim, int cuj, String transition) {
-        if (Trace.isEnabled()) {
-            anim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    Trace.beginAsyncSection(transition, 0);
-                    InteractionJankMonitorWrapper.begin(cuj);
-                    super.onAnimationStart(animation);
-                }
+        anim.addListener(new AnimationSuccessListener() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                Trace.beginAsyncSection(transition, 0);
+                InteractionJankMonitorWrapper.begin(cuj);
+                super.onAnimationStart(animation);
+            }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    super.onAnimationCancel(animation);
-                    InteractionJankMonitorWrapper.cancel(cuj);
-                }
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                super.onAnimationCancel(animation);
+                InteractionJankMonitorWrapper.cancel(cuj);
+            }
 
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    InteractionJankMonitorWrapper.end(cuj);
-                }
+            @Override
+            public void onAnimationSuccess(Animator animator) {
+                InteractionJankMonitorWrapper.end(cuj);
+            }
 
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
-                }
-            });
-        }
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+            }
+        });
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 58937c3..9944270 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -251,7 +251,7 @@
                 return "predictions";
             }
             case SHORTCUTS_CONTAINER: {
-                return "shortcuts";
+                return "deep-shortcuts";
             }
             case FOLDER: {
                 FolderContainer fc = ci.getFolder();
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index be57dec..c3f5c00 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -15,11 +15,15 @@
  */
 package com.android.launcher3.model;
 
+import static android.text.format.DateUtils.DAY_IN_MILLIS;
+import static android.text.format.DateUtils.formatElapsedTime;
+
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_GRID;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
 
 import android.app.prediction.AppPredictionContext;
@@ -29,10 +33,12 @@
 import android.app.prediction.AppTargetEvent;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
+import android.util.Log;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
@@ -40,12 +46,16 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.logging.InstanceId;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.PersistedItemArray;
 import com.android.quickstep.logging.StatsLogCompatManager;
 
@@ -61,6 +71,10 @@
 public class QuickstepModelDelegate extends ModelDelegate implements OnIDPChangeListener {
 
     public static final String LAST_PREDICTION_ENABLED_STATE = "last_prediction_enabled_state";
+    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
+
+    private static final boolean IS_DEBUG = false;
+    private static final String TAG = "QuickstepModelDelegate";
 
     private final PredictorState mAllAppsState =
             new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
@@ -81,6 +95,7 @@
     }
 
     @Override
+    @WorkerThread
     public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
         // TODO: Implement caching and preloading
         super.loadItems(ums, pinnedShortcuts);
@@ -106,6 +121,38 @@
     }
 
     @Override
+    @WorkerThread
+    public void modelLoadComplete() {
+        super.modelLoadComplete();
+
+        // Log snapshot of the model
+        SharedPreferences prefs = getDevicePrefs(mApp.getContext());
+        long lastSnapshotTimeMillis = prefs.getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
+        // Log snapshot only if previous snapshot was older than a day
+        long now = System.currentTimeMillis();
+        if (now - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
+            if (IS_DEBUG) {
+                String elapsedTime = formatElapsedTime((now - lastSnapshotTimeMillis) / 1000);
+                Log.d(TAG, String.format(
+                        "Skipped snapshot logging since previous snapshot was %s old.",
+                        elapsedTime));
+            }
+        } else {
+            IntSparseArrayMap<ItemInfo> itemsIdMap;
+            synchronized (mDataModel) {
+                itemsIdMap = mDataModel.itemsIdMap.clone();
+            }
+            InstanceId instanceId = new InstanceIdSequence().newInstanceId();
+            for (ItemInfo info : itemsIdMap) {
+                FolderInfo parent = info.container > 0
+                        ? (FolderInfo) itemsIdMap.get(info.container) : null;
+                StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
+            }
+            prefs.edit().putLong(LAST_SNAPSHOT_TIME_MILLIS, now).apply();
+        }
+    }
+
+    @Override
     public void validateData() {
         super.validateData();
         if (mAllAppsState.predictor != null) {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 5d227be..6cf3c7a 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;
@@ -95,8 +93,10 @@
 import com.android.quickstep.util.InputConsumerProxy;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.ProtoTracer;
+import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransactionApplier;
+import com.android.quickstep.util.SwipePipToHomeAnimator;
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.views.LiveTileOverlay;
 import com.android.quickstep.views.RecentsView;
@@ -233,6 +233,10 @@
 
     private final Runnable mOnDeferredActivityLaunch = this::onDeferredActivityLaunch;
 
+    private static final long SWIPE_PIP_TO_HOME_DURATION = 425;
+    private SwipePipToHomeAnimator mSwipePipToHomeAnimator;
+    protected boolean mIsSwipingPipToHome;
+
     public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
@@ -618,13 +622,6 @@
 
         updateSysUiFlags(mCurrentShift.value);
         applyWindowTransform();
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            if (mRecentsAnimationTargets != null) {
-                LiveTileOverlay.INSTANCE.update(
-                        mTaskViewSimulator.getCurrentRect(),
-                        mTaskViewSimulator.getCurrentCornerRadius());
-            }
-        }
 
         updateLauncherTransitionProgress();
     }
@@ -800,6 +797,8 @@
     }
 
     private void onSettledOnEndTarget() {
+        // Fast-finish the attaching animation if it's still running.
+        maybeUpdateRecentsAttachedState(false);
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
         switch (endTarget) {
             case HOME:
@@ -1053,25 +1052,46 @@
 
         if (mGestureState.getEndTarget() == HOME) {
             mTaskViewSimulator.setDrawsBelowRecents(false);
-            HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
-            RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
-            windowAnim.addAnimatorListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animator) {
-                    if (mRecentsAnimationController == null) {
-                        // If the recents animation is interrupted, we still end the running
-                        // animation (not canceled) so this is still called. In that case, we can
-                        // skip doing any future work here for the current gesture.
-                        return;
-                    }
-                    // Finalize the state and notify of the change
-                    mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
-                }
-            });
             getOrientationHandler().adjustFloatingIconStartVelocity(velocityPxPerMs);
-            windowAnim.start(mContext, velocityPxPerMs);
+            final RemoteAnimationTargetCompat runningTaskTarget = mRecentsAnimationTargets != null
+                    ? mRecentsAnimationTargets.findTask(mGestureState.getRunningTaskId())
+                    : null;
+            HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(duration);
+            mIsSwipingPipToHome = homeAnimFactory.supportSwipePipToHome()
+                    && runningTaskTarget != null
+                    && runningTaskTarget.pictureInPictureParams != null
+                    && TaskInfoCompat.isAutoEnterPipEnabled(
+                            runningTaskTarget.pictureInPictureParams)
+                    && TaskInfoCompat.getPipSourceRectHint(
+                            runningTaskTarget.pictureInPictureParams) != null;
+            if (mIsSwipingPipToHome) {
+                mSwipePipToHomeAnimator = getSwipePipToHomeAnimator(
+                        homeAnimFactory, runningTaskTarget);
+                mSwipePipToHomeAnimator.setDuration(SWIPE_PIP_TO_HOME_DURATION);
+                mSwipePipToHomeAnimator.setInterpolator(interpolator);
+                mSwipePipToHomeAnimator.setFloatValues(0f, 1f);
+                mSwipePipToHomeAnimator.start();
+                mRunningWindowAnim = RunningWindowAnim.wrap(mSwipePipToHomeAnimator);
+            } else {
+                mSwipePipToHomeAnimator = null;
+                RectFSpringAnim windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+                windowAnim.addAnimatorListener(new AnimationSuccessListener() {
+                    @Override
+                    public void onAnimationSuccess(Animator animator) {
+                        if (mRecentsAnimationController == null) {
+                            // If the recents animation is interrupted, we still end the running
+                            // animation (not canceled) so this is still called. In that case,
+                            // we can skip doing any future work here for the current gesture.
+                            return;
+                        }
+                        // Finalize the state and notify of the change
+                        mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+                    }
+                });
+                windowAnim.start(mContext, velocityPxPerMs);
+                mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
+            }
             homeAnimFactory.playAtomicAnimation(velocityPxPerMs.y);
-            mRunningWindowAnim = RunningWindowAnim.wrap(windowAnim);
             mLauncherTransitionController = null;
         } else {
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
@@ -1115,6 +1135,47 @@
         }
     }
 
+    private SwipePipToHomeAnimator getSwipePipToHomeAnimator(HomeAnimationFactory homeAnimFactory,
+            RemoteAnimationTargetCompat runningTaskTarget) {
+        // Directly animate the app to PiP (picture-in-picture) mode
+        final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
+        final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
+        final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
+                .startSwipePipToHome(taskInfo.topActivity,
+                        TaskInfoCompat.getTopActivityInfo(taskInfo),
+                        runningTaskTarget.pictureInPictureParams,
+                        orientationState.getRecentsActivityRotation(),
+                        mDp.hotseatBarSizePx);
+        final SwipePipToHomeAnimator swipePipToHomeAnimator = new SwipePipToHomeAnimator(
+                runningTaskTarget.taskId,
+                taskInfo.topActivity,
+                runningTaskTarget.leash.getSurfaceControl(),
+                TaskInfoCompat.getPipSourceRectHint(runningTaskTarget.pictureInPictureParams),
+                TaskInfoCompat.getWindowConfigurationBounds(taskInfo),
+                destinationBounds);
+        swipePipToHomeAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                // Ensure Launcher ends in NORMAL state, we intentionally skip other callbacks
+                // since they are not relevant in this swipe-pip-to-home case.
+                homeAnimFactory.createActivityAnimationToHome().dispatchOnStart();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mRecentsAnimationController == null) {
+                    // If the recents animation is interrupted, we still end the running
+                    // animation (not canceled) so this is still called. In that case, we can
+                    // skip doing any future work here for the current gesture.
+                    return;
+                }
+                // Finalize the state and notify of the change
+                mGestureState.setState(STATE_END_TARGET_ANIMATION_FINISHED);
+            }
+        });
+        return swipePipToHomeAnimator;
+    }
+
     private void computeRecentsScrollIfInvisible() {
         if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
             // Views typically don't compute scroll when invisible as an optimization,
@@ -1372,6 +1433,7 @@
             // If there are no targets or the animation not started, then there is nothing to finish
             mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
         } else {
+            maybeFinishSwipePipToHome();
             finishRecentsControllerToHome(
                     () -> mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED));
         }
@@ -1379,6 +1441,22 @@
         doLogGesture(HOME, mRecentsView == null ? null : mRecentsView.getCurrentPageTaskView());
     }
 
+    /**
+     * Resets the {@link #mIsSwipingPipToHome} and notifies SysUI that transition is finished
+     * if applicable. This should happen before {@link #finishRecentsControllerToHome(Runnable)}.
+     */
+    private void maybeFinishSwipePipToHome() {
+        if (mIsSwipingPipToHome && mSwipePipToHomeAnimator != null) {
+            SystemUiProxy.INSTANCE.get(mContext).stopSwipePipToHome(
+                    mSwipePipToHomeAnimator.getComponentName(),
+                    mSwipePipToHomeAnimator.getDestinationBounds());
+            mRecentsAnimationController.setFinishTaskBounds(
+                    mSwipePipToHomeAnimator.getTaskId(),
+                    mSwipePipToHomeAnimator.getDestinationBounds());
+            mIsSwipingPipToHome = false;
+        }
+    }
+
     protected abstract void finishRecentsControllerToHome(Runnable callback);
 
     private void setupLauncherUiAfterSwipeUpToRecentsAnimation() {
@@ -1405,13 +1483,11 @@
                 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(){
@@ -1611,6 +1687,11 @@
             }
             mTaskViewSimulator.apply(mTransformParams);
         }
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mRecentsAnimationTargets != null) {
+            LiveTileOverlay.INSTANCE.update(
+                    mTaskViewSimulator.getCurrentRect(),
+                    mTaskViewSimulator.getCurrentCornerRadius());
+        }
         ProtoTracer.INSTANCE.get(mContext).scheduleFrameUpdate();
     }
 
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 4411455..842fb84 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -56,6 +56,7 @@
             final TaskView runningTaskView = mRecentsView.getRunningTaskView();
             final View workspaceView;
             if (runningTaskView != null
+                    && !mIsSwipingPipToHome
                     && runningTaskView.getTask().key.getComponent() != null) {
                 workspaceView = mActivity.getWorkspace().getFirstMatchForAppClose(
                         runningTaskView.getTask().key.getComponent().getPackageName(),
@@ -140,5 +141,10 @@
             new StaggeredWorkspaceAnim(mActivity, velocity,
                     true /* animateOverviewScrim */).start();
         }
+
+        @Override
+        public boolean supportSwipePipToHome() {
+            return true;
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 32268a4..80308a5 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,19 +15,25 @@
  */
 package com.android.quickstep;
 
+import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.UserManager;
 import android.util.Log;
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
+import com.android.launcher3.util.Executors;
+import com.android.quickstep.logging.SettingsChangeLogger;
 import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
+@TargetApi(Build.VERSION_CODES.R)
 public class QuickstepProcessInitializer extends MainProcessInitializer {
 
     private static final String TAG = "QuickstepProcessInitializer";
+    private static final int SETUP_DELAY_MILLIS = 5000;
 
     public QuickstepProcessInitializer(Context context) { }
 
@@ -51,5 +57,9 @@
         // Elevate GPU priority for Quickstep and Remote animations.
         ThreadedRendererCompat.setContextPriority(
                 ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+
+        // Initialize settings logger after a default timeout
+        Executors.MAIN_EXECUTOR.getHandler()
+                .postDelayed(() -> new SettingsChangeLogger(context), SETUP_DELAY_MILLIS);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 62bbf4d..7de658c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -18,6 +18,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
+import android.graphics.Rect;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
@@ -140,6 +142,18 @@
     }
 
     /**
+     * Sets the final bounds on a Task. This is used by Launcher to notify the system that
+     * animating Activity to PiP has completed and the associated task surface should be updated
+     * accordingly. This should be called before `finish`
+     * @param taskId for which the leash should be updated
+     * @param destinationBounds bounds of the final PiP window
+     */
+    public void setFinishTaskBounds(int taskId, Rect destinationBounds) {
+        UI_HELPER_EXECUTOR.execute(
+                () -> mController.setFinishTaskBounds(taskId, destinationBounds));
+    }
+
+    /**
      * Enables the input consumer to start intercepting touches in the app window.
      */
     public void enableInputConsumer() {
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 7406dea..8f7ec3b 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -165,6 +165,14 @@
         public void update(RectF currentRect, float progress, float radius) { }
 
         public void onCancel() { }
+
+        /**
+         * @return {@code true} if this factory supports animating an Activity to PiP window on
+         * swiping up to home.
+         */
+        public boolean supportSwipePipToHome() {
+            return false;
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 65e89cf..dd0ed8f 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -21,12 +21,14 @@
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityManager;
 
@@ -34,6 +36,7 @@
 
 import com.android.launcher3.FastBitmapDrawable;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.icons.LauncherIcons;
@@ -42,7 +45,6 @@
 import com.android.quickstep.util.TaskKeyLruCache;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskDescriptionCompat;
 
@@ -163,9 +165,8 @@
                         key.getComponent(), key.userId);
             }
             if (activityInfo != null) {
-                entry.contentDescription = ActivityManagerWrapper.getInstance()
-                        .getBadgedContentDescription(activityInfo, task.key.userId,
-                                task.taskDescription);
+                entry.contentDescription = getBadgedContentDescription(
+                        activityInfo, task.key.userId, task.taskDescription);
             }
         }
 
@@ -173,6 +174,21 @@
         return entry;
     }
 
+    private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
+        PackageManager pm = mContext.getPackageManager();
+        String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
+        if (TextUtils.isEmpty(taskLabel)) {
+            taskLabel = Utilities.trim(info.loadLabel(pm));
+        }
+
+        String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm));
+        String badgedApplicationLabel = userId != UserHandle.myUserId()
+                ? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString()
+                : applicationLabel;
+        return applicationLabel.equals(taskLabel)
+                ? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel;
+    }
+
     @WorkerThread
     private Drawable getDefaultIcon(int userId) {
         synchronized (mDefaultIcons) {
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/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 5520ef7..ed761cf 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -41,6 +41,7 @@
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.DeviceProfile;
@@ -129,6 +130,21 @@
         return taskView;
     }
 
+    public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
+            RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
+            PendingAnimation out) {
+        boolean isRunningTask = v.isRunningTask();
+        TransformParams params = null;
+        TaskViewSimulator tsv = null;
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
+            params = v.getRecentsView().getLiveTileParams();
+            tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
+        }
+        createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets,
+                depthController, out, params, tsv);
+    }
+
     /**
      * Creates an animation that controls the window of the opening targets for the recents launch
      * animation.
@@ -136,19 +152,25 @@
     public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets, DepthController depthController,
-            PendingAnimation out) {
+            PendingAnimation out, @Nullable TransformParams params,
+            @Nullable TaskViewSimulator tsv) {
         boolean isQuickSwitch = v.isEndQuickswitchCuj();
         v.setEndQuickswitchCuj(false);
 
-        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+        boolean inLiveTileMode =
+                ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets,
-                        ENABLE_QUICKSTEP_LIVE_TILE.get() ? MODE_CLOSING : MODE_OPENING);
-        targets.addReleaseCheck(applier);
+                        inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
 
-        TransformParams params = new TransformParams()
+        if (params == null) {
+            SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+            targets.addReleaseCheck(applier);
+
+            params = new TransformParams()
                     .setSyncTransactionApplier(applier)
                     .setTargetSet(targets);
+        }
 
         final RecentsView recentsView = v.getRecentsView();
         int taskIndex = recentsView.indexOfChild(v);
@@ -162,8 +184,9 @@
         int displayRotation = DisplayController.getDefaultDisplay(context).getInfo().rotation;
 
         TaskViewSimulator topMostSimulator = null;
-        if (targets.apps.length > 0) {
-            TaskViewSimulator tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
+
+        if (tsv == null && targets.apps.length > 0) {
+            tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
             tsv.setDp(dp);
             tsv.setLayoutRotation(displayRotation, displayRotation);
             tsv.setPreview(targets.apps[targets.apps.length - 1]);
@@ -171,19 +194,24 @@
             tsv.recentsViewScale.value = 1;
             tsv.setScroll(startScroll);
 
+            // Fade in the task during the initial 20% of the animation
+            out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
+                    clampToProgress(LINEAR, 0, 0.2f));
+        }
+
+        if (tsv != null) {
             out.setFloat(tsv.fullScreenProgress,
                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
             out.setFloat(tsv.recentsViewScale,
                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
             out.setInt(tsv, TaskViewSimulator.SCROLL, 0, TOUCH_RESPONSE_INTERPOLATOR);
 
-            out.addOnFrameCallback(() -> tsv.apply(params));
+            TaskViewSimulator finalTsv = tsv;
+            TransformParams finalParams = params;
+            out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
             topMostSimulator = tsv;
         }
 
-        // Fade in the task during the initial 20% of the animation
-        out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, clampToProgress(LINEAR, 0, 0.2f));
-
         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
 
@@ -269,7 +297,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
@@ -302,7 +329,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/interaction/AssistantGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
index 6862f07..5c81e5f 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialController.java
@@ -104,7 +104,13 @@
                         hideFeedback();
                         hideHandCoachingAnimation();
                         showRippleEffect(
-                                () -> mTutorialFragment.changeController(ASSISTANT_COMPLETE));
+                                () -> {
+                                    if (mTutorialFragment.isTutorialComplete()) {
+                                        mTutorialFragment.changeController(ASSISTANT_COMPLETE);
+                                    } else {
+                                        mTutorialFragment.continueTutorial();
+                                    }
+                                });
                         break;
                     case ASSISTANT_NOT_STARTED_BAD_ANGLE:
                         showFeedback(R.string.assistant_gesture_feedback_swipe_not_diagonal);
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index 921e568..161e015 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -130,7 +130,13 @@
                 hideFeedback();
                 hideHandCoachingAnimation();
                 showRippleEffect(
-                        () -> mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE));
+                        () -> {
+                            if (mTutorialFragment.isTutorialComplete()) {
+                                mTutorialFragment.changeController(BACK_NAVIGATION_COMPLETE);
+                            } else {
+                                mTutorialFragment.continueTutorial();
+                            }
+                        });
                 break;
             case BACK_CANCELLED_FROM_LEFT:
                 showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index f8d9d8d..8b6777b 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -15,8 +15,6 @@
  */
 package com.android.quickstep.interaction;
 
-import static com.android.quickstep.interaction.TutorialFragment.KEY_TUTORIAL_TYPE;
-
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -25,11 +23,14 @@
 import android.view.View;
 import android.view.Window;
 
+import androidx.annotation.NonNull;
 import androidx.fragment.app.FragmentActivity;
 
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
+import java.util.ArrayDeque;
+import java.util.Deque;
 import java.util.List;
 
 /** Shows the gesture interactive sandbox in full screen mode. */
@@ -37,6 +38,9 @@
 
     private static final String LOG_TAG = "GestureSandboxActivity";
 
+    private static final String KEY_TUTORIAL_STEPS = "tutorial_steps";
+
+    private Deque<TutorialType> mTutorialSteps;
     private TutorialFragment mFragment;
 
     @Override
@@ -45,7 +49,9 @@
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         setContentView(R.layout.gesture_tutorial_activity);
 
-        mFragment = TutorialFragment.newInstance(getTutorialType(getIntent().getExtras()));
+        Bundle args = savedInstanceState == null ? getIntent().getExtras() : savedInstanceState;
+        mTutorialSteps = getTutorialSteps(args);
+        mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
         getSupportFragmentManager().beginTransaction()
                 .add(R.id.gesture_tutorial_fragment_container, mFragment)
                 .commit();
@@ -72,17 +78,65 @@
         }
     }
 
-    private TutorialType getTutorialType(Bundle extras) {
-        TutorialType defaultType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
+    @Override
+    protected void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
+        savedInstanceState.putStringArray(KEY_TUTORIAL_STEPS, getTutorialStepNames());
+        super.onSaveInstanceState(savedInstanceState);
+    }
 
-        if (extras == null || !extras.containsKey(KEY_TUTORIAL_TYPE)) {
-            return defaultType;
+    /** Returns true iff there aren't anymore tutorial types to display to the user. */
+    public boolean isTutorialComplete() {
+        return mTutorialSteps.isEmpty();
+    }
+
+    /**
+     * Replaces the current TutorialFragment, continuing to the next tutorial step if there is one.
+     *
+     * If there is no following step, the tutorial is closed.
+     */
+    public void continueTutorial() {
+        if (isTutorialComplete()) {
+            mFragment.closeTutorial();
+            return;
         }
-        try {
-            return TutorialType.valueOf(extras.getString(KEY_TUTORIAL_TYPE, ""));
-        } catch (IllegalArgumentException e) {
-            return defaultType;
+        mFragment = TutorialFragment.newInstance(mTutorialSteps.pop());
+        getSupportFragmentManager().beginTransaction()
+            .replace(R.id.gesture_tutorial_fragment_container, mFragment)
+            .runOnCommit(() -> mFragment.onAttachedToWindow())
+            .commit();
+    }
+
+    private String[] getTutorialStepNames() {
+        String[] tutorialStepNames = new String[mTutorialSteps.size()];
+
+        int i = 0;
+        for (TutorialType tutorialStep : mTutorialSteps) {
+            tutorialStepNames[i++] = tutorialStep.name();
         }
+
+        return tutorialStepNames;
+    }
+
+    private Deque<TutorialType> getTutorialSteps(Bundle extras) {
+        Deque<TutorialType> defaultSteps = new ArrayDeque<>();
+        defaultSteps.push(TutorialType.RIGHT_EDGE_BACK_NAVIGATION);
+
+        if (extras == null || !extras.containsKey(KEY_TUTORIAL_STEPS)) {
+            return defaultSteps;
+        }
+
+        String[] tutorialStepNames = extras.getStringArray(KEY_TUTORIAL_STEPS);
+
+        if (tutorialStepNames == null) {
+            return defaultSteps;
+        }
+
+        Deque<TutorialType> tutorialSteps = new ArrayDeque<>();
+        for (String tutorialStepName : tutorialStepNames) {
+            tutorialSteps.addLast(TutorialType.valueOf(tutorialStepName));
+        }
+
+        return tutorialSteps;
     }
 
     private void hideSystemUI() {
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index 0edabd4..95352d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -94,8 +94,13 @@
             case HOME_NAVIGATION:
                 switch (result) {
                     case HOME_GESTURE_COMPLETED: {
-                        animateFakeTaskViewHome(finalVelocity, () ->
-                                mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE));
+                        animateFakeTaskViewHome(finalVelocity, () -> {
+                            if (mTutorialFragment.isTutorialComplete()) {
+                                mTutorialFragment.changeController(HOME_NAVIGATION_COMPLETE);
+                            } else {
+                                mTutorialFragment.continueTutorial();
+                            }
+                        });
                         break;
                     }
                     case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index c636eba..45cbd0b 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -104,8 +104,13 @@
                         showFeedback(R.string.overview_gesture_feedback_swipe_too_far_from_edge);
                         break;
                     case OVERVIEW_GESTURE_COMPLETED:
-                        fadeOutFakeTaskView(true, () ->
-                                mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE));
+                        fadeOutFakeTaskView(true, () -> {
+                            if (mTutorialFragment.isTutorialComplete()) {
+                                mTutorialFragment.changeController(OVERVIEW_NAVIGATION_COMPLETE);
+                            } else {
+                                mTutorialFragment.continueTutorial();
+                            }
+                        });
                         break;
                     case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
                     case HOME_OR_OVERVIEW_CANCELLED:
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index f297d5a..608fe72 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -54,6 +54,7 @@
             fragment = new BackGestureTutorialFragment();
             tutorialType = TutorialType.RIGHT_EDGE_BACK_NAVIGATION;
         }
+
         Bundle args = new Bundle();
         args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType);
         fragment.setArguments(args);
@@ -197,6 +198,20 @@
         return mHandCoachingAnimation;
     }
 
+    void continueTutorial() {
+        if (!(getContext() instanceof GestureSandboxActivity)) {
+            closeTutorial();
+            return;
+        }
+        GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
+
+        if (gestureSandboxActivity == null) {
+            closeTutorial();
+            return;
+        }
+        gestureSandboxActivity.continueTutorial();
+    }
+
     void closeTutorial() {
         FragmentActivity activity = getActivity();
         if (activity != null) {
@@ -207,4 +222,13 @@
     void startSystemNavigationSetting() {
         startActivity(new Intent("com.android.settings.GESTURE_NAVIGATION_SETTINGS"));
     }
+
+    boolean isTutorialComplete() {
+        if (!(getContext() instanceof GestureSandboxActivity)) {
+            return true;
+        }
+        GestureSandboxActivity gestureSandboxActivity = (GestureSandboxActivity) getContext();
+
+        return gestureSandboxActivity == null || gestureSandboxActivity.isTutorialComplete();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
new file mode 100644
index 0000000..0bb0bbc
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.logging;
+
+import static com.android.launcher3.Utilities.getDevicePrefs;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED;
+import static com.android.launcher3.model.QuickstepModelDelegate.LAST_PREDICTION_ENABLED_STATE;
+import static com.android.launcher3.util.SecureSettingsObserver.newNotificationSettingsObserver;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.TypedArray;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.InstanceIdSequence;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.util.SecureSettingsObserver;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.SysUINavigationMode.Mode;
+import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Utility class to log launcher settings changes
+ */
+public class SettingsChangeLogger implements
+        NavigationModeChangeListener, OnSharedPreferenceChangeListener {
+
+    private static final String TAG = "SettingsChangeLogger";
+    private static final String ROOT_TAG = "androidx.preference.PreferenceScreen";
+    private static final String BOOLEAN_PREF = "SwitchPreference";
+
+    private final Context mContext;
+    private final ArrayMap<String, LoggablePref> mLoggablePrefs;
+
+    private Mode mNavMode;
+    private boolean mNotificationDotsEnabled;
+
+    public SettingsChangeLogger(Context context) {
+        mContext = context;
+        mLoggablePrefs = loadPrefKeys(context);
+        mNavMode = SysUINavigationMode.INSTANCE.get(context).addModeChangeListener(this);
+
+        Utilities.getPrefs(context).registerOnSharedPreferenceChangeListener(this);
+        getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+
+        SecureSettingsObserver dotsObserver =
+                newNotificationSettingsObserver(context, this::onNotificationDotsChanged);
+        mNotificationDotsEnabled = dotsObserver.getValue();
+        dispatchUserEvent();
+
+    }
+
+    private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
+        XmlPullParser parser = context.getResources().getXml(R.xml.launcher_preferences);
+        ArrayMap<String, LoggablePref> result = new ArrayMap<>();
+
+        try {
+            AutoInstallsLayout.beginDocument(parser, ROOT_TAG);
+            final int depth = parser.getDepth();
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG
+                    || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                if (BOOLEAN_PREF.equals(parser.getName())) {
+                    TypedArray a = context.obtainStyledAttributes(
+                            Xml.asAttributeSet(parser), R.styleable.LoggablePref);
+                    String key = a.getString(R.styleable.LoggablePref_android_key);
+                    LoggablePref pref = new LoggablePref();
+                    pref.defaultValue =
+                            a.getBoolean(R.styleable.LoggablePref_android_defaultValue, true);
+                    pref.eventIdOn = a.getInt(R.styleable.LoggablePref_logIdOn, 0);
+                    pref.eventIdOff = a.getInt(R.styleable.LoggablePref_logIdOff, 0);
+                    if (pref.eventIdOff > 0 && pref.eventIdOn > 0) {
+                        result.put(key, pref);
+                    }
+                }
+            }
+        } catch (XmlPullParserException | IOException e) {
+            Log.e(TAG, "Error parsing preference xml", e);
+        }
+        return result;
+    }
+
+    private void onNotificationDotsChanged(boolean isDotsEnabled) {
+        mNotificationDotsEnabled = isDotsEnabled;
+        dispatchUserEvent();
+    }
+
+    @Override
+    public void onNavigationModeChanged(Mode newMode) {
+        mNavMode = newMode;
+        dispatchUserEvent();
+    }
+
+    @Override
+    public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+        if (LAST_PREDICTION_ENABLED_STATE.equals(key) || mLoggablePrefs.containsKey(key)) {
+            dispatchUserEvent();
+        }
+    }
+
+    private void dispatchUserEvent() {
+        StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
+                .withInstanceId(new InstanceIdSequence().newInstanceId());
+
+        logger.log(mNotificationDotsEnabled
+                ? LAUNCHER_NOTIFICATION_DOT_ENABLED
+                : LAUNCHER_NOTIFICATION_DOT_DISABLED);
+        logger.log(mNavMode.launcherEvent);
+        logger.log(getDevicePrefs(mContext).getBoolean(LAST_PREDICTION_ENABLED_STATE, true)
+                ? LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED
+                : LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED);
+
+        SharedPreferences prefs = Utilities.getPrefs(mContext);
+        mLoggablePrefs.forEach((key, lp) -> logger.log(() ->
+                prefs.getBoolean(key, lp.defaultValue) ? lp.eventIdOn : lp.eventIdOff));
+    }
+
+    private static class LoggablePref {
+        public boolean defaultValue;
+        public int eventIdOn;
+        public int eventIdOff;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 059d158..d949126 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -16,10 +16,6 @@
 
 package com.android.quickstep.logging;
 
-import static android.text.format.DateUtils.DAY_IN_MILLIS;
-import static android.text.format.DateUtils.formatElapsedTime;
-
-import static com.android.launcher3.Utilities.getDevicePrefs;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.FOLDER;
 import static com.android.launcher3.logger.LauncherAtom.ContainerInfo.ContainerCase.SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
@@ -28,8 +24,6 @@
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
 
-import static java.lang.System.currentTimeMillis;
-
 import android.content.Context;
 import android.util.Log;
 
@@ -44,22 +38,16 @@
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.logging.InstanceId;
-import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.AllAppsList;
 import com.android.launcher3.model.BaseModelUpdateTask;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
-import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.LogConfig;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.Optional;
 import java.util.OptionalInt;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -78,7 +66,6 @@
 
     private static final String TAG = "StatsLog";
     private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.STATSLOG);
-    private static final String LAST_SNAPSHOT_TIME_MILLIS = "LAST_SNAPSHOT_TIME_MILLIS";
     private static final InstanceId DEFAULT_INSTANCE_ID = InstanceId.fakeInstanceId(0);
     // LauncherAtom.ItemInfo.getDefaultInstance() should be used but until launcher proto migrates
     // from nano to lite, bake constant to prevent robo test failure.
@@ -101,71 +88,10 @@
     }
 
     /**
-     * Logs impression of the current workspace with additional launcher events.
+     * Synchronously writes an itemInfo to stats log
      */
-    @Override
-    public void logSnapshot(List<EventEnum> extraEvents) {
-        LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
-                new SnapshotWorker(extraEvents));
-    }
-
-    private class SnapshotWorker extends BaseModelUpdateTask {
-        private final InstanceId mInstanceId;
-        private final List<EventEnum> mExtraEvents;
-
-        SnapshotWorker(List<EventEnum> extraEvents) {
-            mInstanceId = new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
-                    .newInstanceId();
-            this.mExtraEvents = extraEvents;
-        }
-
-        @Override
-        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-            long lastSnapshotTimeMillis = getDevicePrefs(mContext)
-                    .getLong(LAST_SNAPSHOT_TIME_MILLIS, 0);
-            // Log snapshot only if previous snapshot was older than a day
-            if (currentTimeMillis() - lastSnapshotTimeMillis < DAY_IN_MILLIS) {
-                if (IS_VERBOSE) {
-                    String elapsedTime = formatElapsedTime(
-                            (currentTimeMillis() - lastSnapshotTimeMillis) / 1000);
-                    Log.d(TAG, String.format(
-                            "Skipped snapshot logging since previous snapshot was %s old.",
-                            elapsedTime));
-                }
-                return;
-            }
-
-            IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
-            ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
-            ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
-            for (ItemInfo info : workspaceItems) {
-                LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo, mInstanceId);
-            }
-            for (FolderInfo fInfo : folders) {
-                try {
-                    ArrayList<WorkspaceItemInfo> folderContents =
-                            (ArrayList) Executors.MAIN_EXECUTOR.submit(fInfo.contents::clone).get();
-                    for (ItemInfo info : folderContents) {
-                        LauncherAtom.ItemInfo atomInfo = info.buildProto(fInfo);
-                        writeSnapshot(atomInfo, mInstanceId);
-                    }
-                } catch (Exception e) {
-                }
-            }
-            for (ItemInfo info : appWidgets) {
-                LauncherAtom.ItemInfo atomInfo = info.buildProto(null);
-                writeSnapshot(atomInfo, mInstanceId);
-            }
-            mExtraEvents
-                    .forEach(eventName -> logger().withInstanceId(mInstanceId).log(eventName));
-
-            getDevicePrefs(mContext).edit()
-                    .putLong(LAST_SNAPSHOT_TIME_MILLIS, currentTimeMillis()).apply();
-        }
-    }
-
-    private void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
+    @WorkerThread
+    public static void writeSnapshot(LauncherAtom.ItemInfo info, InstanceId instanceId) {
         if (IS_VERBOSE) {
             Log.d(TAG, String.format("\nwriteSnapshot(%d):\n%s", instanceId.getId(), info));
         }
diff --git a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
index d0f6879..da5f59e 100644
--- a/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
+++ b/quickstep/src/com/android/quickstep/util/MotionPauseDetector.java
@@ -193,7 +193,10 @@
                 if (isFirstDetectedPause) {
                     mOnMotionPauseListener.onMotionPauseDetected();
                 }
-                mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+                // Null check again as onMotionPauseDetected() maybe have called clear().
+                if (mOnMotionPauseListener != null) {
+                    mOnMotionPauseListener.onMotionPauseChanged(mIsPaused);
+                }
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
new file mode 100644
index 0000000..87fee79
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.RectEvaluator;
+import android.animation.ValueAnimator;
+import android.content.ComponentName;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
+
+/**
+ * An {@link Animator} that animates an Activity to PiP (picture-in-picture) window when
+ * swiping up (in gesture navigation mode). Note that this class is derived from
+ * {@link com.android.wm.shell.pip.PipAnimationController.PipTransitionAnimator}.
+ *
+ * TODO: consider sharing this class including the animator and leash operations between
+ * Launcher and SysUI. Also, there should be one source of truth for the corner radius of the
+ * PiP window, which would ideally be on SysUI side as well.
+ */
+public class SwipePipToHomeAnimator extends ValueAnimator implements
+        ValueAnimator.AnimatorUpdateListener {
+    private final int mTaskId;
+    private final ComponentName mComponentName;
+    private final SurfaceControl mLeash;
+    private final Rect mStartBounds = new Rect();
+    private final Rect mDestinationBounds = new Rect();
+    private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
+
+    /** for calculating the transform in {@link #onAnimationUpdate(ValueAnimator)} */
+    private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect());
+    private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect());
+    private final Rect mSourceHintRectInsets = new Rect();
+    private final Rect mSourceInsets = new Rect();
+
+    /**
+     * Flag to avoid the double-end problem since the leash would have been released
+     * after the first end call and any further operations upon it would lead to NPE.
+     */
+    private boolean mHasAnimationEnded;
+
+    public SwipePipToHomeAnimator(int taskId,
+            @NonNull ComponentName componentName,
+            @NonNull SurfaceControl leash,
+            @NonNull Rect sourceRectHint,
+            @NonNull Rect startBounds,
+            @NonNull Rect destinationBounds) {
+        mTaskId = taskId;
+        mComponentName = componentName;
+        mLeash = leash;
+        mStartBounds.set(startBounds);
+        mDestinationBounds.set(destinationBounds);
+        mSurfaceTransactionHelper = new PipSurfaceTransactionHelper();
+
+        mSourceHintRectInsets.set(sourceRectHint.left - startBounds.left,
+                sourceRectHint.top - startBounds.top,
+                startBounds.right - sourceRectHint.right,
+                startBounds.bottom - sourceRectHint.bottom);
+
+        addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                SwipePipToHomeAnimator.this.onAnimationEnd();
+            }
+        });
+        addUpdateListener(this);
+    }
+
+    @Override
+    public void onAnimationUpdate(ValueAnimator animator) {
+        if (mHasAnimationEnded) return;
+
+        final float fraction = animator.getAnimatedFraction();
+        final Rect bounds = mRectEvaluator.evaluate(fraction, mStartBounds, mDestinationBounds);
+        final Rect insets = mInsetsEvaluator.evaluate(fraction, mSourceInsets,
+                mSourceHintRectInsets);
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        mSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mStartBounds, bounds, insets);
+        mSurfaceTransactionHelper.resetCornerRadius(tx, mLeash);
+        tx.apply();
+    }
+
+    public int getTaskId() {
+        return mTaskId;
+    }
+
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public Rect getDestinationBounds() {
+        return mDestinationBounds;
+    }
+
+    private void onAnimationEnd() {
+        if (mHasAnimationEnded) return;
+
+        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        mSurfaceTransactionHelper.reset(tx, mLeash, mDestinationBounds);
+        tx.apply();
+        mHasAnimationEnded = true;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 08fe48d..61ba411 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -52,16 +52,16 @@
 
     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();
@@ -72,7 +72,6 @@
     private final BaseActivityInterface mSizeStrategy;
 
     private final Rect mTaskRect = new Rect();
-    private float mOffsetY;
     private boolean mDrawsBelowRecents;
     private final PointF mPivot = new PointF();
     private DeviceProfile mDp;
@@ -89,6 +88,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();
@@ -178,10 +179,6 @@
         }
     }
 
-    public void setOffsetY(float offsetY) {
-        mOffsetY = offsetY;
-    }
-
     public void setDrawsBelowRecents(boolean drawsBelowRecents) {
         mDrawsBelowRecents = drawsBelowRecents;
     }
@@ -298,7 +295,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/LiveTileOverlay.java b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
index c6c2d7e..f6eb0e2 100644
--- a/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -65,6 +65,10 @@
         invalidateSelf();
     }
 
+    public void update(float left, float top, float right, float bottom) {
+        mCurrentRect.set(left, top, right, bottom);
+    }
+
     public void setIcon(Drawable icon) {
         mIcon = icon;
     }
@@ -94,18 +98,16 @@
 
     @Override
     public void draw(Canvas canvas) {
-        if (mCurrentRect != null) {
-            canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
-            if (mIcon != null && mIconAnimationProgress > 0f) {
-                canvas.save();
-                float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
-                        1f).getInterpolation(mIconAnimationProgress);
-                canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
-                        mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
-                canvas.scale(scale, scale);
-                mIcon.draw(canvas);
-                canvas.restore();
-            }
+        canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+        if (mIcon != null && mIconAnimationProgress > 0f) {
+            canvas.save();
+            float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+                    1f).getInterpolation(mIconAnimationProgress);
+            canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+                    mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+            canvas.scale(scale, scale);
+            mIcon.draw(canvas);
+            canvas.restore();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2158e03..264a182 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -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;
@@ -99,7 +100,6 @@
 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;
@@ -114,6 +114,7 @@
 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;
@@ -230,6 +231,7 @@
                     view.setScaleX(scale);
                     view.setScaleY(scale);
                     view.mLastComputedTaskPushOutDistance = null;
+                    view.mLiveTileTaskViewSimulator.recentsViewScale.value = scale;
                     view.updatePageOffsets();
                     view.setTaskViewsSecondaryTranslation(view.mTaskViewsSecondaryTranslation);
                 }
@@ -873,9 +875,14 @@
             // 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();
+            LiveTileOverlay.INSTANCE.update(0, 0, deviceProfile.widthPx, deviceProfile.heightPx);
         }
         if (mRunningTaskTileHidden) {
             setRunningTaskHidden(mRunningTaskTileHidden);
@@ -1540,7 +1547,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();
             });
         }
@@ -1986,6 +1996,7 @@
             TaskView task = getTaskViewAt(i);
             mOrientationHandler.getSecondaryViewTranslate().set(task, translation / getScaleY());
         }
+        mLiveTileTaskViewSimulator.recentsViewSecondaryTranslation.value = translation;
     }
 
     /**
@@ -2084,22 +2095,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;
@@ -2264,6 +2288,10 @@
         return mLiveTileTaskViewSimulator;
     }
 
+    public TransformParams getLiveTileParams() {
+        return mLiveTileParams;
+    }
+
     // TODO: To be removed in a follow up CL
     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
             RecentsAnimationTargets recentsAnimationTargets) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 54a793c..5154018 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -39,6 +39,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.animation.ValueAnimator;
@@ -65,7 +66,6 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
@@ -75,6 +75,7 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
@@ -82,10 +83,12 @@
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.TaskIconCache;
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
+import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
@@ -175,7 +178,7 @@
     private float mCurveScale;
     private float mFullscreenProgress;
     private final FullscreenDrawParams mCurrentFullscreenParams;
-    private final BaseDraggingActivity mActivity;
+    private final StatefulActivity mActivity;
 
     private ObjectAnimator mIconAndDimAnimator;
     private float mIconScaleAnimStartProgress = 0;
@@ -212,18 +215,31 @@
 
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        mActivity = BaseDraggingActivity.fromContext(context);
+        mActivity = StatefulActivity.fromContext(context);
         setOnClickListener((view) -> {
             if (getTask() == null) {
                 return;
             }
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                if (isRunningTask()) {
-                    // TODO: Replace this animation with createRecentsWindowAnimator
-                    createLaunchAnimationForRunningTask().start();
-                } else {
-                    launchTask(true /* animate */);
-                }
+            if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask()) {
+                RecentsView recentsView = getRecentsView();
+                RemoteAnimationTargets targets = recentsView.getLiveTileParams().getTargetSet();
+                recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(false);
+
+                AnimatorSet anim = new AnimatorSet();
+                TaskViewUtils.composeRecentsLaunchAnimator(
+                        anim, this, targets.apps,
+                        targets.wallpapers, true /* launcherClosing */,
+                        mActivity.getStateManager(), recentsView,
+                        recentsView.getDepthController());
+                anim.addListener(new AnimatorListenerAdapter() {
+
+                    @Override
+                    public void onAnimationEnd(Animator animator) {
+                        recentsView.getLiveTileTaskViewSimulator().setDrawsBelowRecents(true);
+                        recentsView.finishRecentsAnimation(false, null);
+                    }
+                });
+                anim.start();
             } else {
                 launchTask(true /* animate */);
             }
diff --git a/res/layout/all_apps_icon.xml b/res/layout/all_apps_icon.xml
index 79fb612..069954c 100644
--- a/res/layout/all_apps_icon.xml
+++ b/res/layout/all_apps_icon.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2015 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,16 +12,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.BubbleTextView
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.launcher3.BubbleTextView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/BaseIcon"
+    style="@style/BaseIcon.AllApps"
     android:id="@+id/icon"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:stateListAnimator="@animator/all_apps_fastscroll_icon_anim"
     launcher:iconDisplay="all_apps"
-    launcher:centerVertically="true"
-    android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
-    android:paddingRight="@dimen/dynamic_grid_cell_padding_x" />
+    launcher:centerVertically="true" />
 
diff --git a/res/layout/search_result_icon.xml b/res/layout/search_result_icon.xml
new file mode 100644
index 0000000..3c1dd49
--- /dev/null
+++ b/res/layout/search_result_icon.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2008 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.SearchResultIcon xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    style="@style/BaseIcon.AllApps"
+    launcher:iconDisplay="all_apps"
+    launcher:centerVertically="true" />
+
diff --git a/res/layout/switch_preference_with_settings.xml b/res/layout/switch_preference_with_settings.xml
index d319561..cd51833 100644
--- a/res/layout/switch_preference_with_settings.xml
+++ b/res/layout/switch_preference_with_settings.xml
@@ -26,7 +26,7 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:src="@drawable/ic_setting"
-        android:tint="@android:color/black"
+        android:forceDarkAllowed="true"
         android:padding="12dp"
         android:background="?android:attr/selectableItemBackgroundBorderless" />
 
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6b0f300..c3ad1eb 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -174,6 +174,14 @@
         <attr name="canThumbDetach" format="boolean" />
     </declare-styleable>
 
+    <declare-styleable name="LoggablePref">
+        <attr name="android:key" />
+        <attr name="android:defaultValue" />
+        <!-- Ground truth of this Pref integer can be found in StatsLogManager -->
+        <attr name="logIdOn" format="integer" />
+        <attr name="logIdOff" format="integer" />
+    </declare-styleable>
+
     <declare-styleable name="PreviewFragment">
         <attr name="android:name" />
         <attr name="android:id" />
diff --git a/res/values/styles.xml b/res/values/styles.xml
index fd3d873..067cf7f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -223,6 +223,16 @@
         <item name="android:lines">1</item>
     </style>
 
+    <!-- Base theme for AllApps BubbleTextViews -->
+    <style name="BaseIcon.AllApps" parent="BaseIcon">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:stateListAnimator">@animator/all_apps_fastscroll_icon_anim</item>
+        <item name="android:paddingLeft">@dimen/dynamic_grid_cell_padding_x</item>
+        <item name="android:paddingRight">@dimen/dynamic_grid_cell_padding_x</item>
+    </style>
+
+
     <!-- Icon displayed on the workspace -->
     <style name="BaseIcon.Workspace" >
         <item name="android:shadowRadius">2.0</item>
diff --git a/res/xml/launcher_preferences.xml b/res/xml/launcher_preferences.xml
index 7e72208..e4bea50 100644
--- a/res/xml/launcher_preferences.xml
+++ b/res/xml/launcher_preferences.xml
@@ -15,7 +15,8 @@
 -->
 
 <androidx.preference.PreferenceScreen
-    xmlns:android="http://schemas.android.com/apk/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto">
 
     <com.android.launcher3.settings.NotificationDotsPreference
         android:key="pref_icon_badging"
@@ -30,19 +31,31 @@
         </intent>
     </com.android.launcher3.settings.NotificationDotsPreference>
 
+    <!--
+      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED(613)
+      LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_DISABLED(614)
+    -->
     <SwitchPreference
         android:key="pref_add_icon_to_home"
         android:title="@string/auto_add_shortcuts_label"
         android:summary="@string/auto_add_shortcuts_description"
         android:defaultValue="true"
-        android:persistent="true" />
+        android:persistent="true"
+        launcher:logIdOn="613"
+        launcher:logIdOff="614" />
 
+    <!--
+      LAUNCHER_HOME_SCREEN_ROTATION_ENABLED(615)
+      LAUNCHER_HOME_SCREEN_ROTATION_DISABLED(616)
+    -->
     <SwitchPreference
         android:key="pref_allowRotation"
         android:title="@string/allow_rotation_title"
         android:summary="@string/allow_rotation_desc"
         android:defaultValue="@bool/allow_rotation"
-        android:persistent="true" />
+        android:persistent="true"
+        launcher:logIdOn="615"
+        launcher:logIdOff="616" />
 
     <androidx.preference.PreferenceScreen
         android:key="pref_developer_options"
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 817d028..1015a32 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
@@ -27,15 +28,18 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
+import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Process;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
 import android.util.Property;
@@ -50,6 +54,8 @@
 
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsSectionDecorator;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
@@ -60,6 +66,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconCache.IconLoadRequest;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -79,7 +86,7 @@
  * too aggressive.
  */
 public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, OnResumeCallback,
-        IconLabelDotView, DraggableView, Reorderable {
+        IconLabelDotView, DraggableView, Reorderable, AllAppsSectionDecorator.SelfDecoratingView {
 
     private static final int DISPLAY_WORKSPACE = 0;
     private static final int DISPLAY_ALL_APPS = 1;
@@ -87,6 +94,8 @@
     private static final int DISPLAY_HERO_APP = 5;
 
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
+    private static final float HIGHLIGHT_SCALE = 1.16f;
+
 
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
@@ -95,6 +104,11 @@
 
     private float mScaleForReorderBounce = 1f;
 
+    protected final Paint mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private final Path mHighlightPath = new Path();
+    protected int mHighlightColor = Color.TRANSPARENT;
+    private final BlurMaskFilter mHighlightShadowFilter;
+
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
             = new Property<BubbleTextView, Float>(Float.TYPE, "dotScale") {
         @Override
@@ -208,6 +222,11 @@
         setEllipsize(TruncateAt.END);
         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
         setTextAlpha(1f);
+
+        int shadowSize = context.getResources().getDimensionPixelSize(
+                R.dimen.blur_size_click_shadow);
+        mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER);
+
     }
 
     @Override
@@ -421,8 +440,38 @@
 
     @Override
     public void onDraw(Canvas canvas) {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mHighlightColor != Color.TRANSPARENT) {
+            int count = canvas.save();
+            drawFocusHighlight(canvas);
+            canvas.restoreToCount(count);
+        }
         super.onDraw(canvas);
-        drawDotIfNecessary(canvas);
+    }
+
+    protected void drawFocusHighlight(Canvas canvas) {
+        boolean isBadged = getTag() instanceof ItemInfo && !Process.myUserHandle().equals(
+                ((ItemInfo) getTag()).user);
+        float insetScale = (HIGHLIGHT_SCALE - 1) / 2;
+        canvas.translate(-getIconSize() * insetScale, -insetScale * getIconSize());
+        float outlineSize = getIconSize() * HIGHLIGHT_SCALE;
+        mHighlightPath.reset();
+        mHighlightPaint.reset();
+        getIconBounds(mDotParams.iconBounds);
+        getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left, mDotParams.iconBounds.top,
+                outlineSize / 2);
+        if (isBadged) {
+            float borderSize = outlineSize - getIconSize();
+            float badgeSize = LauncherIcons.getBadgeSizeForIconSize(getIconSize()) + borderSize;
+            float badgeInset = outlineSize - badgeSize;
+            getShape().addToPath(mHighlightPath, mDotParams.iconBounds.left + badgeInset,
+                    mDotParams.iconBounds.top + badgeInset, badgeSize / 2);
+        }
+        mHighlightPaint.setMaskFilter(mHighlightShadowFilter);
+        mHighlightPaint.setColor(mDotParams.color);
+        canvas.drawPath(mHighlightPath, mHighlightPaint);
+        mHighlightPaint.setMaskFilter(null);
+        mHighlightPaint.setColor(mHighlightColor);
+        canvas.drawPath(mHighlightPath, mHighlightPaint);
     }
 
     /**
@@ -656,8 +705,13 @@
 
         updateIcon(icon);
 
+        ItemInfo itemInfo = (ItemInfo) getTag();
+
         // If the current icon is a placeholder color, animate its update.
-        if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable) {
+        if (mIcon != null
+                && mIcon instanceof PlaceHolderIconDrawable
+                && (itemInfo == null
+                    || itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
             animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon);
         }
 
@@ -787,10 +841,11 @@
 
     @Override
     public SafeCloseable prepareDrawDragView() {
+        int highlightColor = mHighlightColor;
+        mHighlightColor = Color.TRANSPARENT;
         resetIconScale();
         setForceHideDot(true);
-        return () -> {
-        };
+        return () -> mHighlightColor = highlightColor;
     }
 
     private void resetIconScale() {
@@ -827,4 +882,17 @@
         });
         iconUpdateAnimation.start();
     }
+
+
+    @Override
+    public void decorate(int color) {
+        mHighlightColor = color;
+        invalidate();
+    }
+
+    @Override
+    public void removeDecoration() {
+        mHighlightColor = Color.TRANSPARENT;
+        invalidate();
+    }
 }
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index fd4c30c..70d8476 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -79,9 +79,7 @@
         public DraggableView originalView = null;
 
         /** Used for matching DROP event with its corresponding DRAG event on the server side. */
-        public final InstanceId logInstanceId =
-                new InstanceIdSequence(1 << 20 /*InstanceId.INSTANCE_ID_MAX*/)
-                    .newInstanceId();
+        public final InstanceId logInstanceId = new InstanceIdSequence().newInstanceId();
 
         public DragObject(Context context) {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 370bd6f..ff53b5f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -201,7 +201,7 @@
                 DisplayController.getDefaultDisplay(context).getInfo(),
                 getPredefinedDeviceProfiles(context, gridName));
 
-        Info myInfo = new Info(context, display);
+        Info myInfo = new Info(display);
         DisplayOption myDisplayOption = invDistWeightedInterpolate(
                 myInfo, getPredefinedDeviceProfiles(context, gridName));
 
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 2973cf7..aeed16a 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -188,6 +188,9 @@
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
+        final Bundle extra = new Bundle();
+        extra.putString(LauncherSettings.Settings.EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
+        result.setExtras(extra);
         result.setNotificationUri(getContext().getContentResolver(), uri);
 
         return result;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 58a418e..d2758f5 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -358,6 +358,8 @@
 
         public static final String EXTRA_VALUE = "value";
 
+        public static final String EXTRA_DB_NAME = "db_name";
+
         public static Bundle call(ContentResolver cr, String method) {
             return call(cr, method, null /* arg */);
         }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 98328cf..777ea3c 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -139,6 +139,10 @@
 
     public static final int DEFAULT_PAGE = 0;
 
+    private static final int DEFAULT_SMARTSPACE_HEIGHT = 1;
+
+    private static final int EXPANDED_SMARTSPACE_HEIGHT = 2;
+
     private LayoutTransition mLayoutTransition;
     @Thunk final WallpaperManager mWallpaperManager;
 
@@ -507,7 +511,10 @@
                     .inflate(R.layout.search_container_workspace, firstPage, false);
         }
 
-        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(), 1);
+        int cellVSpan = FeatureFlags.EXPANDED_SMARTSPACE.get()
+                ? EXPANDED_SMARTSPACE_HEIGHT : DEFAULT_SMARTSPACE_HEIGHT;
+        CellLayout.LayoutParams lp = new CellLayout.LayoutParams(0, 0, firstPage.getCountX(),
+                cellVSpan);
         lp.canReorder = false;
         if (!firstPage.addViewToCellLayout(qsb, 0, R.id.search_container_workspace, lp, true)) {
             Log.e(TAG, "Failed to add to item at (0, 0) to CellLayout");
@@ -2078,40 +2085,40 @@
         mLastReorderY = -1;
     }
 
-   /*
-    *
-    * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
-    * coordinate space. The argument xy is modified with the return result.
-    */
-   private void mapPointFromSelfToChild(View v, float[] xy) {
-       xy[0] = xy[0] - v.getLeft();
-       xy[1] = xy[1] - v.getTop();
-   }
+    /*
+     *
+     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
+     * coordinate space. The argument xy is modified with the return result.
+     */
+    private void mapPointFromSelfToChild(View v, float[] xy) {
+        xy[0] = xy[0] - v.getLeft();
+        xy[1] = xy[1] - v.getTop();
+    }
 
-   boolean isPointInSelfOverHotseat(int x, int y) {
-       mTempFXY[0] = x;
-       mTempFXY[1] = y;
-       mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
-       View hotseat = mLauncher.getHotseat();
-       return mTempFXY[0] >= hotseat.getLeft() &&
-               mTempFXY[0] <= hotseat.getRight() &&
-               mTempFXY[1] >= hotseat.getTop() &&
-               mTempFXY[1] <= hotseat.getBottom();
-   }
+    boolean isPointInSelfOverHotseat(int x, int y) {
+        mTempFXY[0] = x;
+        mTempFXY[1] = y;
+        mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, mTempFXY, true);
+        View hotseat = mLauncher.getHotseat();
+        return mTempFXY[0] >= hotseat.getLeft()
+                && mTempFXY[0] <= hotseat.getRight()
+                && mTempFXY[1] >= hotseat.getTop()
+                && mTempFXY[1] <= hotseat.getBottom();
+    }
 
     /**
      * Updates the point in {@param xy} to point to the co-ordinate space of {@param layout}
      * @param layout either hotseat of a page in workspace
      * @param xy the point location in workspace co-ordinate space
      */
-   private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
-       if (mLauncher.isHotseatLayout(layout)) {
-           mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
-           mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
-       } else {
-           mapPointFromSelfToChild(layout, xy);
-       }
-   }
+    private void mapPointFromDropLayout(CellLayout layout, float[] xy) {
+        if (mLauncher.isHotseatLayout(layout)) {
+            mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(this, xy, true);
+            mLauncher.getDragLayer().mapCoordInSelfToDescendant(layout, xy);
+        } else {
+            mapPointFromSelfToChild(layout, xy);
+        }
+    }
 
     private boolean isDragWidget(DragObject d) {
         return (d.dragInfo instanceof LauncherAppWidgetInfo ||
@@ -2393,9 +2400,9 @@
             spanY = mDragInfo.spanY;
         }
 
-        final int container = mLauncher.isHotseatLayout(cellLayout) ?
-                LauncherSettings.Favorites.CONTAINER_HOTSEAT :
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP;
+        final int container = mLauncher.isHotseatLayout(cellLayout)
+                ? LauncherSettings.Favorites.CONTAINER_HOTSEAT
+                : LauncherSettings.Favorites.CONTAINER_DESKTOP;
         final int screenId = getIdForScreen(cellLayout);
         if (!mLauncher.isHotseatLayout(cellLayout)
                 && screenId != getScreenIdForPageIndex(mCurrentPage)
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 75ab00a..5d5e017 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -16,7 +16,7 @@
 package com.android.launcher3.allapps;
 
 import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
-import static com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import static com.android.launcher3.allapps.AllAppsGridAdapter.SearchAdapterItem;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -57,6 +57,7 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.search.SearchEventTracker;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusedItemDecorator;
 import com.android.launcher3.model.data.AppInfo;
@@ -67,9 +68,7 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
-
-import java.util.function.IntConsumer;
+import com.android.systemui.plugins.shared.SearchTarget;
 
 /**
  * The all apps view container.
@@ -546,13 +545,9 @@
             return mLauncher.startActivitySafely(v, headerItem.getIntent(), headerItem);
         }
         AdapterItem focusedItem = getActiveRecyclerView().getApps().getFocusedChild();
-        if (focusedItem instanceof AdapterItemWithPayload) {
-            IntConsumer onSelection =
-                    ((AdapterItemWithPayload) focusedItem).getSelectionHandler();
-            if (onSelection != null) {
-                onSelection.accept(SearchTargetEvent.QUICK_SELECT);
-                return true;
-            }
+        if (focusedItem instanceof SearchAdapterItem) {
+            SearchTarget searchTarget = ((SearchAdapterItem) focusedItem).getSearchTarget();
+            SearchEventTracker.INSTANCE.get(getContext()).quickSelect(searchTarget);
         }
         if (focusedItem.appInfo != null) {
             ItemInfo itemInfo = focusedItem.appInfo;
@@ -585,6 +580,10 @@
         int padding = mHeader.getMaxTranslation();
         for (int i = 0; i < mAH.length; i++) {
             mAH[i].padding.top = padding;
+            if (FeatureFlags.ENABLE_DEVICE_SEARCH.get() && mUsingTabs) {
+                //add extra space between tabs and recycler view
+                mAH[i].padding.top += mLauncher.getDeviceProfile().edgeMarginPx;
+            }
             mAH[i].applyPadding();
         }
     }
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 8bc8e53..f773191 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -20,8 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Bundle;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -37,29 +35,22 @@
 import androidx.core.view.accessibility.AccessibilityEventCompat;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 import androidx.core.view.accessibility.AccessibilityRecordCompat;
-import androidx.lifecycle.LiveData;
 import androidx.recyclerview.widget.GridLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
-import androidx.slice.Slice;
-import androidx.slice.widget.SliceLiveData;
 import androidx.slice.widget.SliceView;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
 import com.android.launcher3.allapps.search.SearchSectionInfo;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.views.HeroSearchResultView;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.launcher3.views.SearchSliceWrapper;
 import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.List;
-import java.util.function.IntConsumer;
 
 /**
  * The grid view adapter of all the apps.
@@ -100,9 +91,11 @@
 
     public static final int VIEW_TYPE_SEARCH_SUGGEST = 1 << 13;
 
+    public static final int VIEW_TYPE_SEARCH_ICON = 1 << 14;
+
     // 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;
+    public static final int VIEW_TYPE_MASK_ICON = VIEW_TYPE_ICON | VIEW_TYPE_SEARCH_ICON;
 
     /**
      * ViewHolder for each icon.
@@ -192,56 +185,25 @@
                     || viewType == VIEW_TYPE_SEARCH_PEOPLE
                     || viewType == VIEW_TYPE_SEARCH_THUMBNAIL
                     || viewType == VIEW_TYPE_SEARCH_ICON_ROW
+                    || viewType == VIEW_TYPE_SEARCH_ICON
                     || viewType == VIEW_TYPE_SEARCH_SUGGEST;
         }
     }
 
     /**
      * Extension of AdapterItem that contains an extra payload specific to item
-     *
-     * @param <T> Play load Type
      */
-    public static class AdapterItemWithPayload<T> extends AdapterItem {
-        private T mPayload;
-        private String mSearchSessionId;
-        private AllAppsSearchPlugin mPlugin;
-        private IntConsumer mSelectionHandler;
+    public static class SearchAdapterItem extends AdapterItem {
+        private SearchTarget mSearchTarget;
 
-        public AllAppsSearchPlugin getPlugin() {
-            return mPlugin;
-        }
-
-        public void setPlugin(AllAppsSearchPlugin plugin) {
-            mPlugin = plugin;
-        }
-
-        public AdapterItemWithPayload(T payload, int type, AllAppsSearchPlugin plugin) {
-            mPayload = payload;
+        public SearchAdapterItem(SearchTarget searchTarget, int type) {
+            mSearchTarget = searchTarget;
             viewType = type;
-            mPlugin = plugin;
         }
 
-        public void setSelectionHandler(IntConsumer runnable) {
-            mSelectionHandler = runnable;
+        public SearchTarget getSearchTarget() {
+            return mSearchTarget;
         }
-
-        public void setSearchSessionId(String searchSessionId) {
-            mSearchSessionId = searchSessionId;
-        }
-
-        public String getSearchSessionId() {
-            return mSearchSessionId;
-        }
-
-        public IntConsumer getSelectionHandler() {
-            return mSelectionHandler;
-        }
-
-        public T getPayload() {
-            return mPayload;
-        }
-
-
     }
 
     /**
@@ -426,11 +388,8 @@
                         R.layout.all_apps_icon, parent, false);
                 icon.setLongPressTimeoutFactor(1f);
                 icon.setOnFocusChangeListener(mIconFocusListener);
-                if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                    icon.setOnClickListener(mOnIconClickListener);
-                    icon.setOnLongClickListener(mOnIconLongClickListener);
-                }
-
+                icon.setOnClickListener(mOnIconClickListener);
+                icon.setOnLongClickListener(mOnIconLongClickListener);
                 // Ensure the all apps icon height matches the workspace icons in portrait mode.
                 icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
                 return new ViewHolder(icon);
@@ -446,6 +405,9 @@
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.all_apps_divider, parent, false));
+            case VIEW_TYPE_SEARCH_ICON:
+                return new ViewHolder(mLayoutInflater.inflate(
+                        R.layout.search_result_icon, parent, false));
             case VIEW_TYPE_SEARCH_CORPUS_TITLE:
                 return new ViewHolder(
                         mLayoutInflater.inflate(R.layout.search_section_title, parent, false));
@@ -480,6 +442,10 @@
 
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
+        if (FeatureFlags.ENABLE_DEVICE_SEARCH.get()
+                && holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
+            ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
+        }
         switch (holder.getItemViewType()) {
             case VIEW_TYPE_ICON:
                 AdapterItem adapterItem = mApps.getAdapterItems().get(position);
@@ -487,34 +453,6 @@
                 BubbleTextView icon = (BubbleTextView) holder.itemView;
                 icon.reset();
                 icon.applyFromApplicationInfo(info);
-                if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
-                    break;
-                }
-                //TODO: replace with custom TopHitBubbleTextView with support for both shortcut
-                // and apps
-                if (adapterItem instanceof AdapterItemWithPayload) {
-                    AdapterItemWithPayload item = (AdapterItemWithPayload) adapterItem;
-                    item.setSelectionHandler(type -> {
-                        SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
-                                type, item.position, item.getSearchSessionId());
-                        e.bundle = HeroSearchResultView.getAppBundle(info);
-                        if (item.getPlugin() != null) {
-                            item.getPlugin().notifySearchTargetEvent(e);
-                        }
-                    });
-                    icon.setOnClickListener(view -> {
-                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
-                        mOnIconClickListener.onClick(view);
-                    });
-                    icon.setOnLongClickListener(view -> {
-                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
-                        return mOnIconLongClickListener.onLongClick(view);
-                    });
-                }
-                else {
-                    icon.setOnClickListener(mOnIconClickListener);
-                    icon.setOnLongClickListener(mOnIconLongClickListener);
-                }
                 break;
             case VIEW_TYPE_EMPTY_SEARCH:
                 TextView emptyViewText = (TextView) holder.itemView;
@@ -532,39 +470,25 @@
                 break;
             case VIEW_TYPE_SEARCH_SLICE:
                 SliceView sliceView = (SliceView) holder.itemView;
-                AdapterItemWithPayload<Uri> slicePayload =
-                        (AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
-                sliceView.setOnSliceActionListener((info1, s) -> {
-                    if (slicePayload.getPlugin() != null) {
-                        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
-                                SearchTarget.ItemType.SETTINGS_SLICE,
-                                SearchTargetEvent.CHILD_SELECT, slicePayload.position,
-                                slicePayload.getSearchSessionId());
-                        searchTargetEvent.bundle = new Bundle();
-                        searchTargetEvent.bundle.putParcelable("uri", slicePayload.getPayload());
-                        slicePayload.getPlugin().notifySearchTargetEvent(searchTargetEvent);
-                    }
-                });
-                try {
-                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher,
-                            slicePayload.getPayload());
-                    liveData.observe((Launcher) mLauncher, sliceView);
-                    sliceView.setTag(liveData);
-                } catch (Exception ignored) {
-                }
+                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_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:
-                AdapterItemWithPayload item =
-                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position);
-                PayloadResultHandler payloadResultView = (PayloadResultHandler) holder.itemView;
-                payloadResultView.setup(item);
+                SearchAdapterItem item =
+                        (SearchAdapterItem) mApps.getAdapterItems().get(position);
+                SearchTargetHandler payloadResultView = (SearchTargetHandler) holder.itemView;
+                payloadResultView.applySearchTarget(item.getSearchTarget());
                 break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
@@ -576,17 +500,15 @@
     public void onViewRecycled(@NonNull ViewHolder holder) {
         super.onViewRecycled(holder);
         if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
-        if (holder.itemView instanceof BubbleTextView) {
-            BubbleTextView icon = (BubbleTextView) holder.itemView;
-            icon.setOnClickListener(null);
-            icon.setOnLongClickListener(null);
-        } else if (holder.itemView instanceof SliceView) {
+        if (holder.itemView instanceof AllAppsSectionDecorator.SelfDecoratingView) {
+            ((AllAppsSectionDecorator.SelfDecoratingView) holder.itemView).removeDecoration();
+        }
+        if (holder.itemView instanceof SliceView) {
             SliceView sliceView = (SliceView) holder.itemView;
-            sliceView.setOnSliceActionListener(null);
-            if (sliceView.getTag() instanceof LiveData) {
-                LiveData sliceLiveData = (LiveData) sliceView.getTag();
-                sliceLiveData.removeObservers((Launcher) mLauncher);
+            if (sliceView.getTag() instanceof SearchSliceWrapper) {
+                ((SearchSliceWrapper) sliceView.getTag()).destroy();
             }
+            sliceView.setTag(null);
         }
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsPagedView.java b/src/com/android/launcher3/allapps/AllAppsPagedView.java
index eae9c0a..e2550f5 100644
--- a/src/com/android/launcher3/allapps/AllAppsPagedView.java
+++ b/src/com/android/launcher3/allapps/AllAppsPagedView.java
@@ -25,11 +25,11 @@
 
 public class AllAppsPagedView extends PagedView<PersonalWorkSlidingTabStrip> {
 
-  final static float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
-  final static float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
-  final static float TOUCH_SLOP_DAMPING_FACTOR = 4;
+    static final float START_DAMPING_TOUCH_SLOP_ANGLE = (float) Math.PI / 6;
+    static final float MAX_SWIPE_ANGLE = (float) Math.PI / 3;
+    static final float TOUCH_SLOP_DAMPING_FACTOR = 4;
 
-  public AllAppsPagedView(Context context) {
+    public AllAppsPagedView(Context context) {
         this(context, null);
     }
 
@@ -42,6 +42,7 @@
         int topPadding = FeatureFlags.ENABLE_DEVICE_SEARCH.get() ? 0
                 : context.getResources().getDimensionPixelOffset(
                         R.dimen.all_apps_header_top_padding);
+        setPadding(0, topPadding, 0, 0);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 72b6d94..1fa43d0 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.SparseIntArray;
 import android.view.MotionEvent;
 import android.view.View;
@@ -45,6 +46,8 @@
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
 public class AllAppsRecyclerView extends BaseRecyclerView {
+    private static final String TAG = "AllAppsContainerView";
+    private static final boolean DEBUG = true;
 
     private AlphabeticalAppsList mApps;
     private final int mNumAppsPerRow;
@@ -131,7 +134,9 @@
         if (mEmptySearchBackground != null && mEmptySearchBackground.getAlpha() > 0) {
             mEmptySearchBackground.draw(c);
         }
-
+        if (DEBUG) {
+            Log.d(TAG, "onDraw at = " + System.currentTimeMillis());
+        }
         super.onDraw(c);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
index c131697..3c81811 100644
--- a/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
+++ b/src/com/android/launcher3/allapps/AllAppsSectionDecorator.java
@@ -54,6 +54,9 @@
         int i = 0;
         while (i < itemCount) {
             View view = parent.getChildAt(i);
+            if (view instanceof SelfDecoratingView) {
+                ((SelfDecoratingView) view).removeDecoration();
+            }
             int position = parent.getChildAdapterPosition(view);
             AllAppsGridAdapter.AdapterItem adapterItem = adapterItems.get(position);
             if (adapterItem.searchSectionInfo != null) {
@@ -93,7 +96,7 @@
             int index = mAppsView.getApps().getFocusedChildIndex();
             AppsGridLayoutManager layoutManager = (AppsGridLayoutManager)
                     mAppsView.getActiveRecyclerView().getLayoutManager();
-            if (layoutManager.findFirstVisibleItemPosition() == index
+            if (layoutManager.findFirstVisibleItemPosition() <= index
                     && index < parent.getChildCount()) {
                 decorationHandler.onFocusDraw(c, parent.getChildAt(index));
             }
@@ -156,6 +159,10 @@
             if (view == null) {
                 return;
             }
+            if (view instanceof SelfDecoratingView) {
+                ((SelfDecoratingView) view).decorate(mFocusColor);
+                return;
+            }
             mPaint.setColor(mFocusColor);
             canvas.drawRoundRect(view.getLeft(), view.getTop(),
                     view.getRight(), view.getBottom(), mRadius, mRadius, mPaint);
@@ -169,4 +176,18 @@
         }
     }
 
+    /**
+     * An interface for a view to draw highlight indicator
+     */
+    public interface SelfDecoratingView {
+        /**
+         * Removes decorations drawing if focus is acquired by another view
+         */
+        void removeDecoration();
+
+        /**
+         * Draws highlight indicator on view.
+         */
+        void decorate(int focusColor);
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 8c059d5..2450787 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -178,16 +178,46 @@
     /**
      * Sets results list for search
      */
-    public boolean setSearchResults(ArrayList<AdapterItem> f) {
-        if (f == null || mSearchResults != f) {
-            boolean same = mSearchResults != null && mSearchResults.equals(f);
-            mSearchResults = f;
+    public boolean setSearchResults(ArrayList<AdapterItem> results) {
+        if (results == null || mSearchResults != results) {
+            boolean same = mSearchResults != null && mSearchResults.equals(results);
+            mSearchResults = results;
             onAppsUpdated();
             return !same;
         }
         return false;
     }
 
+    public boolean appendSearchResults(ArrayList<AdapterItem> results) {
+        if (mSearchResults != null && results != null && results.size() > 0) {
+            updateSearchAdapterItems(results, mSearchResults.size());
+            refreshRecyclerView();
+            return true;
+        }
+        return false;
+    }
+
+    void updateSearchAdapterItems(ArrayList<AdapterItem> list, int offset) {
+        SearchSectionInfo lastSection = null;
+        for (int i = 0; i < list.size(); i++) {
+            AdapterItem adapterItem = list.get(i);
+            adapterItem.position = offset + i;
+            mAdapterItems.add(adapterItem);
+            if (adapterItem.searchSectionInfo != lastSection) {
+                if (adapterItem.searchSectionInfo != null) {
+                    adapterItem.searchSectionInfo.setPosStart(adapterItem.position);
+                }
+                if (lastSection != null) {
+                    lastSection.setPosEnd(adapterItem.position - 1);
+                }
+                lastSection = adapterItem.searchSectionInfo;
+            }
+            if (adapterItem.isCountedForAccessibility()) {
+                mAccessibilityResultsCount++;
+            }
+        }
+    }
+
     /**
      * Updates internals when the set of apps are updated.
      */
@@ -294,28 +324,7 @@
             }
             appSection.setPosEnd(mApps.isEmpty() ? appSection.getPosStart() : position - 1);
         } else {
-            List<AppInfo> appInfos = new ArrayList<>();
-            SearchSectionInfo lastSection = null;
-            for (int i = 0; i < mSearchResults.size(); i++) {
-                AdapterItem adapterItem = mSearchResults.get(i);
-                adapterItem.position = i;
-                mAdapterItems.add(adapterItem);
-                if (adapterItem.searchSectionInfo != lastSection) {
-                    if (adapterItem.searchSectionInfo != null) {
-                        adapterItem.searchSectionInfo.setPosStart(i);
-                    }
-                    if (lastSection != null) {
-                        lastSection.setPosEnd(i - 1);
-                    }
-                    lastSection = adapterItem.searchSectionInfo;
-                }
-                if (AllAppsGridAdapter.isIconViewType(adapterItem.viewType)) {
-                    appInfos.add(adapterItem.appInfo);
-                }
-                if (adapterItem.isCountedForAccessibility()) {
-                    mAccessibilityResultsCount++;
-                }
-            }
+            updateSearchAdapterItems(mSearchResults, 0);
             if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) {
                 // Append the search market item
                 if (hasNoFilteredResults()) {
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 82c4db4..bc5a5f2 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -30,13 +30,11 @@
 import com.android.launcher3.ExtendedEditText;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.systemui.plugins.AllAppsSearchPlugin;
 import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -196,9 +194,16 @@
         /**
          * Called when the search from primary source is complete.
          *
-         * @param items sorted list of search result adapter items.
+         * @param items sorted list of search result adapter items
          */
-        void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items);
+        void onSearchResult(String query, ArrayList<AdapterItem> items);
+
+        /**
+         * Called when the search from secondary source is complete.
+         *
+         * @param items sorted list of search result adapter items
+         */
+        void onAppendSearchResult(String query, ArrayList<AdapterItem> items);
 
         /**
          * Called when the search results should be cleared.
@@ -208,50 +213,20 @@
 
     /**
      * An interface for supporting dynamic search results
-     *
-     * @param <T> Type of payload
      */
-    public interface PayloadResultHandler<T> {
-        /**
-         * Updates View using Adapter's payload
-         */
+    public interface SearchTargetHandler {
 
-        default void setup(AdapterItemWithPayload<T> adapterItemWithPayload) {
-            Object[] targetInfo = getTargetInfo();
-            if (targetInfo != null) {
-                targetInfo[0] = adapterItemWithPayload.getSearchSessionId();
-                targetInfo[1] = adapterItemWithPayload.position;
-            }
-            applyAdapterInfo(adapterItemWithPayload);
+        /**
+         * Update view using values from {@link SearchTarget}
+         */
+        void applySearchTarget(SearchTarget searchTarget);
+
+        /**
+         * Handles selection of SearchTarget
+         */
+        default void handleSelection(int eventType) {
         }
 
-        void applyAdapterInfo(AdapterItemWithPayload<T> adapterItemWithPayload);
-
-        /**
-         * Gets object created by {@link PayloadResultHandler#createTargetInfo()}
-         */
-        Object[] getTargetInfo();
-
-        /**
-         * Creates a wrapper object to hold searchSessionId and item position
-         */
-        default Object[] createTargetInfo() {
-            return new Object[2];
-        }
-
-        /**
-         * Generates a SearchTargetEvent object for a PayloadHandlerView
-         */
-        default SearchTargetEvent getSearchTargetEvent(SearchTarget.ItemType itemType,
-                int eventType) {
-            Object[] targetInfo = getTargetInfo();
-            if (targetInfo == null) return null;
-
-            String searchSessionId = (String) targetInfo[0];
-            int position = (int) targetInfo[1];
-            return new SearchTargetEvent(itemType, eventType,
-                    position, searchSessionId);
-        }
     }
 
 
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 7518521..000ccbb 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -42,7 +42,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.allapps.SearchUiManager;
@@ -173,7 +173,7 @@
     }
 
     @Override
-    public void onSearchResult(String query, ArrayList<AllAppsGridAdapter.AdapterItem> items) {
+    public void onSearchResult(String query, ArrayList<AdapterItem> items) {
         if (items != null) {
             mApps.setSearchResults(items);
             notifyResultChanged();
@@ -182,6 +182,14 @@
     }
 
     @Override
+    public void onAppendSearchResult(String query, ArrayList<AdapterItem> items) {
+        if (items != null) {
+            mApps.appendSearchResults(items);
+            notifyResultChanged();
+        }
+    }
+
+    @Override
     public void clearSearchResult() {
         if (mApps.setSearchResults(null)) {
             notifyResultChanged();
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
index dc9c155..84688e1 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchPipeline.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.allapps.search;
 
 import android.content.Context;
+import android.os.CancellationSignal;
 
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItem;
@@ -47,11 +48,12 @@
     }
 
     @Override
-    public void performSearch(String query, Consumer<ArrayList<AdapterItem>> callback) {
+    public void query(String input, Consumer<ArrayList<AdapterItem>> callback,
+            CancellationSignal cancellationSignal) {
         mLauncherAppState.getModel().enqueueModelUpdateTask(new BaseModelUpdateTask() {
             @Override
             public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
-                List<AppInfo> matchingResults = getTitleMatchResult(apps.data, query);
+                List<AppInfo> matchingResults = getTitleMatchResult(apps.data, input);
                 callback.accept(getAdapterItems(matchingResults));
             }
         });
diff --git a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
index 5ed7de5..3bddace 100644
--- a/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
+++ b/src/com/android/launcher3/allapps/search/DefaultAppSearchAlgorithm.java
@@ -46,8 +46,10 @@
     @Override
     public void doSearch(final String query,
             final AllAppsSearchBarController.Callbacks callback) {
-        mAppsSearchPipeline.performSearch(query,
-                results -> mResultHandler.post(() -> callback.onSearchResult(query, results)));
+        mAppsSearchPipeline.query(query,
+                results -> mResultHandler.post(
+                        () -> callback.onSearchResult(query, results)),
+                null);
     }
 
     public static boolean matches(AppInfo info, String query, StringMatcher matcher) {
diff --git a/src/com/android/launcher3/allapps/search/SearchEventTracker.java b/src/com/android/launcher3/allapps/search/SearchEventTracker.java
new file mode 100644
index 0000000..c276434
--- /dev/null
+++ b/src/com/android/launcher3/allapps/search/SearchEventTracker.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 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.util.Executors.UI_HELPER_EXECUTOR;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.allapps.search.AllAppsSearchBarController.SearchTargetHandler;
+import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+import java.util.WeakHashMap;
+
+/**
+ * A singleton class to track and report search events to search provider
+ */
+public class SearchEventTracker {
+    @Nullable
+    private AllAppsSearchPlugin mPlugin;
+    private final WeakHashMap<SearchTarget, SearchTargetHandler>
+            mCallbacks = new WeakHashMap<>();
+
+    public static final MainThreadInitializedObject<SearchEventTracker> INSTANCE =
+            new MainThreadInitializedObject<>(SearchEventTracker::new);
+
+    private SearchEventTracker(Context context) {
+    }
+
+    /**
+     * Returns instance of SearchEventTracker
+     */
+    public static SearchEventTracker getInstance(Context context) {
+        return SearchEventTracker.INSTANCE.get(context);
+    }
+
+    /**
+     * Sets current connected plugin for event reporting
+     */
+    public void setPlugin(@Nullable AllAppsSearchPlugin plugin) {
+        mPlugin = plugin;
+    }
+
+    /**
+     * Sends SearchTargetEvent to search provider
+     */
+    public void notifySearchTargetEvent(SearchTargetEvent searchTargetEvent) {
+        if (mPlugin != null) {
+            UI_HELPER_EXECUTOR.post(() -> mPlugin.notifySearchTargetEvent(searchTargetEvent));
+        }
+    }
+
+    /**
+     * Registers a {@link SearchTargetHandler} to handle quick launch for specified SearchTarget.
+     */
+    public void registerWeakHandler(SearchTarget searchTarget, SearchTargetHandler targetHandler) {
+        mCallbacks.put(searchTarget, targetHandler);
+    }
+
+    /**
+     * Handles quick select for SearchTarget
+     */
+    public void quickSelect(SearchTarget searchTarget) {
+        SearchTargetHandler searchTargetHandler = mCallbacks.get(searchTarget);
+        if (searchTargetHandler != null) {
+            searchTargetHandler.handleSelection(SearchTargetEvent.QUICK_SELECT);
+        }
+    }
+
+    /**
+     * flushes all registered quick select handlers
+     */
+    public void clearHandlers() {
+        mCallbacks.clear();
+    }
+}
diff --git a/src/com/android/launcher3/allapps/search/SearchPipeline.java b/src/com/android/launcher3/allapps/search/SearchPipeline.java
index 545f0e3..3516a41 100644
--- a/src/com/android/launcher3/allapps/search/SearchPipeline.java
+++ b/src/com/android/launcher3/allapps/search/SearchPipeline.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.allapps.search;
 
+import android.os.CancellationSignal;
+
 import com.android.launcher3.allapps.AllAppsGridAdapter;
 
 import java.util.ArrayList;
@@ -23,10 +25,13 @@
 /**
  * An interface for handling search within pipeline
  */
+// Remove when System Service API is added.
 public interface SearchPipeline {
 
     /**
      * Perform query
      */
-    void performSearch(String query, Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> cb);
+    void query(String input,
+            Consumer<ArrayList<AllAppsGridAdapter.AdapterItem>> callback,
+            CancellationSignal cancellationSignal);
 }
diff --git a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
index e026e84..464df68 100644
--- a/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
+++ b/src/com/android/launcher3/allapps/search/SearchSectionInfo.java
@@ -22,7 +22,7 @@
  */
 public class SearchSectionInfo {
 
-    private String mTitle;
+    private String mSectionId;
     private SectionDecorationHandler mDecorationHandler;
 
     public int getPosStart() {
@@ -48,8 +48,8 @@
         this(null);
     }
 
-    public SearchSectionInfo(String title) {
-        mTitle = title;
+    public SearchSectionInfo(String sectionId) {
+        mSectionId = sectionId;
     }
 
     public void setDecorationHandler(SectionDecorationHandler sectionDecorationHandler) {
@@ -62,9 +62,9 @@
     }
 
     /**
-     * Returns the section's title
+     * Returns the section's ID
      */
-    public String getTitle() {
-        return mTitle == null ? "" : mTitle;
+    public String getSectionId() {
+        return mSectionId == null ? "" : mSectionId;
     }
 }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 58b0a87..8e6c2a7 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -182,9 +182,13 @@
             "Uses a separate recents activity instead of using the integrated recents+Launcher UI");
 
     public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(
-            "ENABLE_MINIMAL_DEVICE", true,
+            "ENABLE_MINIMAL_DEVICE", false,
             "Allow user to toggle minimal device mode in launcher.");
 
+    public static final BooleanFlag EXPANDED_SMARTSPACE = new DeviceFlag(
+            "EXPANDED_SMARTSPACE", false, "Expands smartspace height to two rows. "
+              + "Any apps occupying the first row will be removed from workspace.");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2c5bf32..7e90769 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -29,8 +29,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ResourceBasedOverride;
 
-import java.util.List;
-
 /**
  * Handles the user event logging in R+.
  *
@@ -463,10 +461,4 @@
                 context.getApplicationContext(), R.string.stats_log_manager_class);
         return mgr;
     }
-
-    /**
-     * Logs impression of the current workspace with additional launcher events.
-     */
-    public void logSnapshot(List<EventEnum> additionalEvents) {
-    }
 }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index a27ac23..532834e 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -445,7 +445,8 @@
             if (item.screenId == Workspace.FIRST_SCREEN_ID) {
                 // Mark the first row as occupied (if the feature is enabled)
                 // in order to account for the QSB.
-                screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN);
+                int spanY = FeatureFlags.EXPANDED_SMARTSPACE.get() ? 2 : 1;
+                screen.markCells(0, 0, countX + 1, spanY, FeatureFlags.QSB_ON_FIRST_SCREEN);
             }
             occupied.put(item.screenId, screen);
         }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index c0d5882..b108788 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -41,6 +41,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.text.TextUtils;
@@ -130,6 +131,7 @@
 
     private final Set<PackageUserKey> mPendingPackages = new HashSet<>();
     private boolean mItemsDeleted = false;
+    private String mDbName;
 
     public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
             ModelDelegate modelDelegate, LoaderResults results) {
@@ -274,12 +276,20 @@
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
                 loadFolderNames();
             }
-            sanitizeData();
+
+            // Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
+            // sanitizeData should not be invoked if the workspace is loaded from a db different
+            // from the main db as defined in the invariant device profile.
+            // (e.g. both grid preview and minimal device mode uses a different db)
+            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
+                sanitizeData();
+            }
 
             verifyNotStopped();
             updateHandler.finish();
             logger.addSplit("finish icon update");
 
+            mModelDelegate.modelLoadComplete();
             transaction.commit();
         } catch (CancellationException e) {
             // Loader stopped, ignore
@@ -349,7 +359,9 @@
             final LoaderCursor c = new LoaderCursor(
                     contentResolver.query(contentUri, null, selection, null, null), contentUri,
                     mApp, mUserManagerState);
-
+            final Bundle extras = c.getExtras();
+            mDbName = extras == null
+                    ? null : extras.getString(LauncherSettings.Settings.EXTRA_DB_NAME);
             try {
                 final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                         LauncherSettings.Favorites.APPWIDGET_ID);
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/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index f4b059d..47d214d 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -275,7 +275,8 @@
         launchBackTutorialPreference.setSummary("Learn how to use the Back gesture");
         launchBackTutorialPreference.setOnPreferenceClickListener(preference -> {
             startActivity(launchSandboxIntent.putExtra(
-                    "tutorial_type", "RIGHT_EDGE_BACK_NAVIGATION"));
+                    "tutorial_steps",
+                    new String[] {"RIGHT_EDGE_BACK_NAVIGATION"}));
             return true;
         });
         sandboxCategory.addPreference(launchBackTutorialPreference);
@@ -284,7 +285,9 @@
         launchHomeTutorialPreference.setTitle("Launch Home Tutorial");
         launchHomeTutorialPreference.setSummary("Learn how to use the Home gesture");
         launchHomeTutorialPreference.setOnPreferenceClickListener(preference -> {
-            startActivity(launchSandboxIntent.putExtra("tutorial_type", "HOME_NAVIGATION"));
+            startActivity(launchSandboxIntent.putExtra(
+                    "tutorial_steps",
+                    new String[] {"HOME_NAVIGATION"}));
             return true;
         });
         sandboxCategory.addPreference(launchHomeTutorialPreference);
@@ -293,7 +296,9 @@
         launchOverviewTutorialPreference.setTitle("Launch Overview Tutorial");
         launchOverviewTutorialPreference.setSummary("Learn how to use the Overview gesture");
         launchOverviewTutorialPreference.setOnPreferenceClickListener(preference -> {
-            startActivity(launchSandboxIntent.putExtra("tutorial_type", "OVERVIEW_NAVIGATION"));
+            startActivity(launchSandboxIntent.putExtra(
+                    "tutorial_steps",
+                    new String[] {"OVERVIEW_NAVIGATION"}));
             return true;
         });
         sandboxCategory.addPreference(launchOverviewTutorialPreference);
@@ -302,7 +307,9 @@
         launchAssistantTutorialPreference.setTitle("Launch Assistant Tutorial");
         launchAssistantTutorialPreference.setSummary("Learn how to use the Assistant gesture");
         launchAssistantTutorialPreference.setOnPreferenceClickListener(preference -> {
-            startActivity(launchSandboxIntent.putExtra("tutorial_type", "ASSISTANT"));
+            startActivity(launchSandboxIntent.putExtra(
+                    "tutorial_steps",
+                    new String[] {"ASSISTANT"}));
             return true;
         });
         sandboxCategory.addPreference(launchAssistantTutorialPreference);
@@ -311,7 +318,9 @@
         launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
         launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
         launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
-            startActivity(launchSandboxIntent.putExtra("tutorial_type", "SANDBOX_MODE"));
+            startActivity(launchSandboxIntent.putExtra(
+                    "tutorial_steps",
+                    new String[] {"SANDBOX_MODE"}));
             return true;
         });
         sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 17f02be..ad6710f 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -42,13 +42,13 @@
 public class LandscapePagedViewHandler implements PagedOrientationHandler {
 
     @Override
-    public int getPrimaryValue(int x, int y) {
-        return y;
+    public <T> T getPrimaryValue(T x, T y) {
+        return x;
     }
 
     @Override
-    public int getSecondaryValue(int x, int y) {
-        return x;
+    public <T> T getSecondaryValue(T x, T y) {
+        return y;
     }
 
     @Override
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/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 355c949..3ab736a 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -31,6 +31,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Utilities;
+
 import java.util.ArrayList;
 
 /**
@@ -157,13 +159,13 @@
         private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
         private DisplayController.Info mInfo;
 
-        private DisplayHolder(Context displayContext) {
+        private DisplayHolder(Context displayContext, Display display) {
             mDisplayContext = displayContext;
             // Note that the Display object must be obtained from DisplayManager which is
             // associated to the display context, so the Display is isolated from Activity and
             // Application to provide the actual state of device that excludes the additional
             // adjustment and override.
-            mInfo = new DisplayController.Info(mDisplayContext);
+            mInfo = new DisplayController.Info(display);
             mId = mInfo.id;
         }
 
@@ -180,22 +182,31 @@
         }
 
         protected void handleOnChange() {
+            Display display = Utilities.ATLEAST_R
+                    ? mDisplayContext.getDisplay()
+                    : mDisplayContext
+                        .getSystemService(DisplayManager.class)
+                        .getDisplay(mId);
+            if (display == null) {
+                return;
+            }
+
             Info oldInfo = mInfo;
-            Info info = new Info(mDisplayContext);
+            Info newInfo = new Info(display);
 
             int change = 0;
-            if (info.hasDifferentSize(oldInfo)) {
+            if (newInfo.hasDifferentSize(oldInfo)) {
                 change |= CHANGE_SIZE;
             }
-            if (oldInfo.rotation != info.rotation) {
+            if (newInfo.rotation != oldInfo.rotation) {
                 change |= CHANGE_ROTATION;
             }
-            if (info.singleFrameMs != oldInfo.singleFrameMs) {
+            if (newInfo.singleFrameMs != oldInfo.singleFrameMs) {
                 change |= CHANGE_FRAME_DELAY;
             }
 
             if (change != 0) {
-                mInfo = info;
+                mInfo = newInfo;
                 final int flags = change;
                 MAIN_EXECUTOR.execute(() -> notifyChange(flags));
             }
@@ -216,7 +227,7 @@
             // Use application context to create display context so that it can have its own
             // Resources.
             Context displayContext = context.getApplicationContext().createDisplayContext(display);
-            return new DisplayHolder(displayContext);
+            return new DisplayHolder(displayContext, display);
         }
     }
 
@@ -244,12 +255,7 @@
             this.metrics = metrics;
         }
 
-        private Info(Context context) {
-            this(context, context.getSystemService(DisplayManager.class)
-                    .getDisplay(DEFAULT_DISPLAY));
-        }
-
-        public Info(Context context, Display display) {
+        public Info(Display display) {
             id = display.getDisplayId();
             rotation = display.getRotation();
 
@@ -262,7 +268,8 @@
             display.getRealSize(realSize);
             display.getCurrentSizeRange(smallestSize, largestSize);
 
-            metrics = context.getResources().getDisplayMetrics();
+            metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
         }
 
         private boolean hasDifferentSize(Info info) {
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
index 91337ba..a098df9 100644
--- a/src/com/android/launcher3/views/HeroSearchResultView.java
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -20,10 +20,8 @@
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
-import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.graphics.Point;
-import android.os.Bundle;
 import android.util.AttributeSet;
 import android.util.Pair;
 import android.view.View;
@@ -37,8 +35,9 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+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;
@@ -48,24 +47,28 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.touch.ItemLongClickListener;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
+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,
-        PayloadResultHandler<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> {
+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 final Object[] mTargetInfo = createTargetInfo();
-    BubbleTextView mBubbleTextView;
-    View mIconView;
-    BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
-    AllAppsSearchPlugin mPlugin;
+
+    private SearchTarget mSearchTarget;
+    private BubbleTextView mBubbleTextView;
+    private View mIconView;
+    private BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
+
 
     public HeroSearchResultView(Context context) {
         super(context);
@@ -106,35 +109,39 @@
                             grid.allAppsIconSizePx));
             bubbleTextView.setOnClickListener(view -> {
                 WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
-                SearchTargetEvent event = getSearchTargetEvent(
-                        SearchTarget.ItemType.APP_HERO,
-                        SearchTargetEvent.CHILD_SELECT);
-                event.bundle = getAppBundle(itemInfo);
-                event.bundle.putString("shortcut_id", itemInfo.getDeepShortcutId());
-                if (mPlugin != null) {
-                    mPlugin.notifySearchTargetEvent(event);
-                }
+                SearchTargetEvent event = new SearchTargetEvent.Builder(mSearchTarget,
+                        SearchTargetEvent.CHILD_SELECT).setShortcutPosition(itemInfo.rank).build();
+                SearchEventTracker.getInstance(getContext()).notifySearchTargetEvent(event);
                 launcher.getItemOnClickListener().onClick(view);
             });
         }
     }
 
-    /**
-     * Apply {@link ItemInfo} for appIcon and shortcut Icons
-     */
     @Override
-    public void applyAdapterInfo(
-            AdapterItemWithPayload<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> adapterItem) {
-        mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
+    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(adapterItem.appInfo);
-        List<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcutDetails = adapterItem.getPayload();
+        mIconView.setTag(appInfo);
         LauncherAppState appState = LauncherAppState.getInstance(getContext());
         for (int i = 0; i < mDeepShortcutTextViews.length; i++) {
             BubbleTextView shortcutView = mDeepShortcutTextViews[i];
-            mDeepShortcutTextViews[i].setVisibility(shortcutDetails.size() > i ? VISIBLE : GONE);
-            if (i < shortcutDetails.size()) {
-                Pair<ShortcutInfo, ItemInfoWithIcon> p = shortcutDetails.get(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(() -> {
@@ -144,13 +151,7 @@
                 });
             }
         }
-        mPlugin = adapterItem.getPlugin();
-        adapterItem.setSelectionHandler(this::handleSelection);
-    }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
     }
 
     @Override
@@ -188,38 +189,21 @@
             mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
                     draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
 
-            SearchTargetEvent event = mContainer.getSearchTargetEvent(
-                    SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
-            event.bundle = getAppBundle(itemInfo);
-            if (mContainer.mPlugin != null) {
-                mContainer.mPlugin.notifySearchTargetEvent(event);
-            }
-
+            SearchTargetEvent event = new SearchTargetEvent.Builder(mContainer.mSearchTarget,
+                    SearchTargetEvent.LONG_PRESS).build();
+            SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(event);
             return false;
         }
     }
 
-    private void handleSelection(int eventType) {
+    @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);
 
-        SearchTargetEvent event = getSearchTargetEvent(
-                SearchTarget.ItemType.APP_HERO, eventType);
-        event.bundle = getAppBundle(itemInfo);
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(event);
-        }
-    }
-
-    /**
-     * Helper method to generate {@link SearchTargetEvent} bundle from {@link ItemInfo}
-     */
-    public static Bundle getAppBundle(ItemInfo itemInfo) {
-        Bundle b = new Bundle();
-        b.putParcelable(Intent.EXTRA_COMPONENT_NAME, itemInfo.getTargetComponent());
-        b.putParcelable(Intent.EXTRA_USER, itemInfo.user);
-        return b;
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
 }
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 15ff2f5..77cec80 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -42,7 +42,7 @@
  */
 public class ScrimView<T extends Launcher> extends View implements Insettable, OnChangeListener {
 
-    private static final float SCRIM_ALPHA = .8f;
+    private static final float SCRIM_ALPHA = .75f;
     protected final T mLauncher;
     private final WallpaperColorInfo mWallpaperColorInfo;
     protected final int mEndScrim;
diff --git a/src/com/android/launcher3/views/SearchResultIcon.java b/src/com/android/launcher3/views/SearchResultIcon.java
new file mode 100644
index 0000000..ea06d5b
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultIcon.java
@@ -0,0 +1,100 @@
+/*
+ * 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.util.AttributeSet;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
+import com.android.launcher3.model.data.AppInfo;
+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;
+
+/**
+ * A {@link BubbleTextView} representing a single cell result in AllApps
+ */
+public class SearchResultIcon extends BubbleTextView implements
+        AllAppsSearchBarController.SearchTargetHandler, View.OnClickListener,
+        View.OnLongClickListener {
+
+
+    public static final String TARGET_TYPE_APP = "app";
+
+    private final Launcher mLauncher;
+
+    private SearchTarget mSearchTarget;
+
+    public SearchResultIcon(Context context) {
+        this(context, null, 0);
+    }
+
+    public SearchResultIcon(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchResultIcon(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mLauncher = Launcher.getLauncher(getContext());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        setLongPressTimeoutFactor(1f);
+        setOnFocusChangeListener(mLauncher.getFocusHandler());
+        setOnClickListener(this);
+        setOnLongClickListener(this);
+        getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
+    }
+
+    @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);
+        }
+    }
+
+    @Override
+    public void handleSelection(int eventType) {
+        mLauncher.getItemOnClickListener().onClick(this);
+        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
+    }
+
+    @Override
+    public void onClick(View view) {
+        handleSelection(SearchTargetEvent.SELECT);
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        SearchEventTracker.INSTANCE.get(mLauncher).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, SearchTargetEvent.LONG_PRESS).build());
+        return ItemLongClickListener.INSTANCE_ALL_APPS.onLongClick(view);
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java
index 6d9c86a..bdbe890 100644
--- a/src/com/android/launcher3/views/SearchResultIconRow.java
+++ b/src/com/android/launcher3/views/SearchResultIconRow.java
@@ -23,9 +23,9 @@
 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.Bundle;
 import android.util.AttributeSet;
 import android.widget.EditText;
 
@@ -35,8 +35,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 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;
@@ -44,25 +44,30 @@
 import com.android.launcher3.model.data.RemoteActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.launcher3.util.Themes;
 import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTarget.ItemType;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
 /**
  * A view representing a stand alone shortcut search result
  */
 public class SearchResultIconRow extends DoubleShadowBubbleTextView implements
-        AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
+        AllAppsSearchBarController.SearchTargetHandler {
 
-    private final Object[] mTargetInfo = createTargetInfo();
-    private final int mCustomIconResId;
+
+    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 boolean mMatchesInset;
 
-    private ShortcutInfo mShortcutInfo;
-    private AllAppsSearchPlugin mPlugin;
-    private AdapterItemWithPayload<SearchTarget> mAdapterItem;
+    private SearchTarget mSearchTarget;
 
+    @Nullable private Drawable mCustomIcon;
 
     public SearchResultIconRow(@NonNull Context context) {
         this(context, null, 0);
@@ -78,10 +83,15 @@
         super(context, attrs, defStyleAttr);
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.SearchResultIconRow, defStyleAttr, 0);
-        mCustomIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
         mMatchesInset = a.getBoolean(R.styleable.SearchResultIconRow_matchTextInsetWithQuery,
                 false);
 
+        int customIconResId = a.getResourceId(R.styleable.SearchResultIconRow_customIcon, 0);
+
+        if (customIconResId != 0) {
+            mCustomIcon = Launcher.getLauncher(context).getDrawable(customIconResId);
+        }
+
         a.recycle();
     }
 
@@ -100,36 +110,38 @@
         }
     }
 
+    @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 applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
-        if (mAdapterItem != null) {
-            mAdapterItem.setSelectionHandler(null);
-        }
-        mAdapterItem = adapterItemWithPayload;
-        SearchTarget payload = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
+    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));
 
-        if (payload.mRemoteAction != null) {
-            prepareUsingRemoteAction(payload.mRemoteAction,
-                    payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
-                    payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START),
-                    payload.type == ItemType.ACTION);
-        } else {
-            prepareUsingShortcutInfo(payload.shortcuts.get(0));
+        } else if (type.equals(TARGET_TYPE_SHORTCUT)) {
+            prepareUsingShortcutInfo(searchTarget.getShortcutInfos().get(0));
         }
         setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
     }
 
     private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
-        mShortcutInfo = shortcutInfo;
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(shortcutInfo, getContext());
         applyFromWorkspaceItem(workspaceItemInfo);
         LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
         if (!loadIconFromResource()) {
             MODEL_EXECUTOR.execute(() -> {
-                launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
+                launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, shortcutInfo);
                 reapplyItemInfoAsync(workspaceItemInfo);
             });
         }
@@ -165,43 +177,28 @@
     }
 
     private boolean loadIconFromResource() {
-        if (mCustomIconResId == 0) return false;
-        setIcon(Launcher.getLauncher(getContext()).getDrawable(mCustomIconResId));
+        if (mCustomIcon == null) return false;
+        setIcon(mCustomIcon);
         return true;
     }
 
     void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
-        MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon));
+        MAIN_EXECUTOR.post(() -> {
+            reapplyItemInfo(itemInfoWithIcon);
+            mCustomIcon = getIcon();
+        });
     }
 
     @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
-    private void handleSelection(int eventType) {
+    public void handleSelection(int eventType) {
         ItemInfo itemInfo = (ItemInfo) getTag();
         Launcher launcher = Launcher.getLauncher(getContext());
-        final SearchTargetEvent searchTargetEvent;
         if (itemInfo instanceof WorkspaceItemInfo) {
             ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
-            searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.SHORTCUT,
-                    eventType);
-            searchTargetEvent.shortcut = mShortcutInfo;
         } else {
-            RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
-            ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
-            searchTargetEvent = getSearchTargetEvent(ItemType.ACTION,
-                    eventType);
-            searchTargetEvent.bundle = new Bundle();
-            searchTargetEvent.remoteAction = remoteItemInfo.getRemoteAction();
-            searchTargetEvent.bundle.putBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START,
-                    remoteItemInfo.shouldStartInLauncher());
-            searchTargetEvent.bundle.putString(SearchTarget.REMOTE_ACTION_TOKEN,
-                    remoteItemInfo.getToken());
+            ItemClickHandler.onClickRemoteAction(launcher, (RemoteActionItemInfo) itemInfo);
         }
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(searchTargetEvent);
-        }
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultPeopleView.java b/src/com/android/launcher3/views/SearchResultPeopleView.java
index f20b080..e499bd5 100644
--- a/src/com/android/launcher3/views/SearchResultPeopleView.java
+++ b/src/com/android/launcher3/views/SearchResultPeopleView.java
@@ -42,11 +42,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
 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.systemui.plugins.AllAppsSearchPlugin;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
@@ -56,7 +55,9 @@
  * A view representing a single people search result in all apps
  */
 public class SearchResultPeopleView extends LinearLayout implements
-        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+        AllAppsSearchBarController.SearchTargetHandler {
+
+    public static final String TARGET_TYPE_PEOPLE = "people";
 
     private final int mIconSize;
     private final int mButtonSize;
@@ -64,9 +65,10 @@
     private View mIconView;
     private TextView mTitleView;
     private ImageButton[] mProviderButtons = new ImageButton[3];
-    private AllAppsSearchPlugin mPlugin;
     private Intent mIntent;
-    private final Object[] mTargetInfo = createTargetInfo();
+
+
+    private SearchTarget mSearchTarget;
 
     public SearchResultPeopleView(Context context) {
         this(context, null, 0);
@@ -103,10 +105,9 @@
     }
 
     @Override
-    public void applyAdapterInfo(
-            AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
-        Bundle payload = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
+    public void applySearchTarget(SearchTarget searchTarget) {
+        mSearchTarget = searchTarget;
+        Bundle payload = searchTarget.getExtras();
         mTitleView.setText(payload.getString("title"));
         mIntent = payload.getParcelable("intent");
         Bitmap contactIcon = payload.getParcelable("icon");
@@ -125,7 +126,7 @@
             if (providers != null && i < providers.size()) {
                 Bundle provider = providers.get(i);
                 Intent intent = provider.getParcelable("intent");
-                setupProviderButton(button, provider, intent, adapterItemWithPayload);
+                setupProviderButton(button, provider, intent);
                 UI_HELPER_EXECUTOR.post(() -> {
                     String pkg = provider.getString("package_name");
                     Drawable appIcon = getAppIcon(pkg);
@@ -138,13 +139,13 @@
                 button.setVisibility(GONE);
             }
         }
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
     }
 
     /**
-     *  Normalizes the bitmap to look like rounded App Icon
-     *  TODO(b/170234747) to support styling, generate adaptive icon drawable and generate
-     *  bitmap from it.
+     * Normalizes the bitmap to look like rounded App Icon
+     * TODO(b/170234747) to support styling, generate adaptive icon drawable and generate
+     * bitmap from it.
      */
     private Bitmap roundBitmap(Bitmap icon) {
         final RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
@@ -180,42 +181,25 @@
         }
     }
 
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
-    private void setupProviderButton(ImageButton button, Bundle provider, Intent intent,
-            AllAppsGridAdapter.AdapterItem adapterItem) {
+    private void setupProviderButton(ImageButton button, Bundle provider, Intent intent) {
         Launcher launcher = Launcher.getLauncher(getContext());
         button.setOnClickListener(b -> {
             launcher.startActivitySafely(b, intent, null);
-            SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
-                    SearchTarget.ItemType.PEOPLE,
-                    SearchTargetEvent.CHILD_SELECT);
-            searchTargetEvent.bundle = new Bundle();
-            searchTargetEvent.bundle.putParcelable("intent", intent);
-            searchTargetEvent.bundle.putString("title", mTitleView.getText().toString());
-            searchTargetEvent.bundle.putBundle("provider", provider);
-            if (mPlugin != null) {
-                mPlugin.notifySearchTargetEvent(searchTargetEvent);
-            }
+            Bundle bundle = new Bundle();
+            bundle.putBundle("provider", provider);
+            SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                    new SearchTargetEvent.Builder(mSearchTarget,
+                            SearchTargetEvent.CHILD_SELECT).setExtras(bundle).build());
         });
     }
 
-
-    private void handleSelection(int eventType) {
+    @Override
+    public void handleSelection(int eventType) {
         if (mIntent != null) {
             Launcher launcher = Launcher.getLauncher(getContext());
             launcher.startActivitySafely(this, mIntent, null);
-            SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.PEOPLE,
-                    eventType);
-            searchTargetEvent.bundle = new Bundle();
-            searchTargetEvent.bundle.putParcelable("intent", mIntent);
-            searchTargetEvent.bundle.putString("title", mTitleView.getText().toString());
-            if (mPlugin != null) {
-                mPlugin.notifySearchTargetEvent(searchTargetEvent);
-            }
+            SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                    new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
         }
     }
 }
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
index c7133fd..86ed436 100644
--- a/src/com/android/launcher3/views/SearchResultPlayItem.java
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -41,11 +41,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.Themes;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
@@ -57,10 +56,13 @@
  * A View representing a PlayStore item.
  */
 public class SearchResultPlayItem extends LinearLayout implements
-        AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+        AllAppsSearchBarController.SearchTargetHandler {
+
+    public static final String TARGET_TYPE_PLAY = "play";
 
     private static final int BITMAP_CROP_MASK_COLOR = 0xff424242;
-
+    final Paint mIconPaint = new Paint();
+    final Rect mTempRect = new Rect();
     private final DeviceProfile mDeviceProfile;
     private View mIconView;
     private TextView mTitleView;
@@ -68,11 +70,8 @@
     private Button mPreviewButton;
     private String mPackageName;
     private boolean mIsInstantGame;
-    private AllAppsSearchPlugin mPlugin;
-    private final Object[] mTargetInfo = createTargetInfo();
 
-    final Paint mIconPaint = new Paint();
-    final Rect mTempRect = new Rect();
+    private SearchTarget mSearchTarget;
 
 
     public SearchResultPlayItem(Context context) {
@@ -105,14 +104,35 @@
         iconParams.height = mDeviceProfile.allAppsIconSizePx;
         iconParams.width = mDeviceProfile.allAppsIconSizePx;
         setOnClickListener(view -> handleSelection(SearchTargetEvent.SELECT));
-
     }
 
+
+    private Bitmap getRoundedBitmap(Bitmap bitmap) {
+        final int iconSize = bitmap.getWidth();
+        final float radius = Themes.getDialogCornerRadius(getContext());
+
+        Bitmap output = BitmapRenderer.createHardwareBitmap(iconSize, iconSize, (canvas) -> {
+            mTempRect.set(0, 0, iconSize, iconSize);
+            final RectF rectF = new RectF(mTempRect);
+
+            mIconPaint.setAntiAlias(true);
+            mIconPaint.reset();
+            canvas.drawARGB(0, 0, 0, 0);
+            mIconPaint.setColor(BITMAP_CROP_MASK_COLOR);
+            canvas.drawRoundRect(rectF, radius, radius, mIconPaint);
+
+            mIconPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
+            canvas.drawBitmap(bitmap, mTempRect, mTempRect, mIconPaint);
+        });
+        return output;
+    }
+
+
     @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
-        Bundle bundle = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+    public void applySearchTarget(SearchTarget searchTarget) {
+        mSearchTarget = searchTarget;
+        Bundle bundle = searchTarget.getExtras();
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
         if (bundle.getString("package", "").equals(mPackageName)) {
             return;
         }
@@ -143,33 +163,6 @@
         });
     }
 
-
-    private Bitmap getRoundedBitmap(Bitmap bitmap) {
-        final int iconSize = bitmap.getWidth();
-        final float radius = Themes.getDialogCornerRadius(getContext());
-
-        Bitmap output = BitmapRenderer.createHardwareBitmap(iconSize, iconSize, (canvas) -> {
-            mTempRect.set(0, 0, iconSize, iconSize);
-            final RectF rectF = new RectF(mTempRect);
-
-            mIconPaint.setAntiAlias(true);
-            mIconPaint.reset();
-            canvas.drawARGB(0, 0, 0, 0);
-            mIconPaint.setColor(BITMAP_CROP_MASK_COLOR);
-            canvas.drawRoundRect(rectF, radius, radius, mIconPaint);
-
-            mIconPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
-            canvas.drawBitmap(bitmap, mTempRect, mTempRect, mIconPaint);
-        });
-        return output;
-    }
-
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
     private void showIfNecessary(TextView textView, @Nullable String string) {
         if (string == null || string.isEmpty()) {
             textView.setVisibility(GONE);
@@ -179,7 +172,8 @@
         }
     }
 
-    private void handleSelection(int eventType) {
+    @Override
+    public void handleSelection(int eventType) {
         if (mPackageName == null) return;
         Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(
                 "https://play.google.com/store/apps/details?id="
@@ -205,12 +199,7 @@
     }
 
     private void logSearchEvent(int eventType) {
-        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
-                SearchTarget.ItemType.PLAY_RESULTS, eventType);
-        searchTargetEvent.bundle = new Bundle();
-        searchTargetEvent.bundle.putString("package_name", mPackageName);
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(searchTargetEvent);
-        }
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
 }
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
index 0fe0a43..326c23d 100644
--- a/src/com/android/launcher3/views/SearchSectionHeaderView.java
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -21,14 +21,16 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.systemui.plugins.shared.SearchTarget;
 
 /**
  * Header text view that shows a title for a given section in All apps search
  */
 public class SearchSectionHeaderView extends TextView implements
-        AllAppsSearchBarController.PayloadResultHandler<String> {
+        AllAppsSearchBarController.SearchTargetHandler {
+    public static final String TARGET_TYPE_SECTION_HEADER = "section_header";
+
     public SearchSectionHeaderView(Context context) {
         super(context);
     }
@@ -43,8 +45,8 @@
     }
 
     @Override
-    public void applyAdapterInfo(AllAppsGridAdapter.AdapterItemWithPayload<String> adapterItem) {
-        String title = adapterItem.getPayload();
+    public void applySearchTarget(SearchTarget searchTarget) {
+        String title = searchTarget.getExtras().getString("title");
         if (title == null || !title.isEmpty()) {
             setText(title);
             setVisibility(VISIBLE);
@@ -52,9 +54,4 @@
             setVisibility(INVISIBLE);
         }
     }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return null;
-    }
 }
diff --git a/src/com/android/launcher3/views/SearchSettingsRowView.java b/src/com/android/launcher3/views/SearchSettingsRowView.java
index a1a0172..30f686c 100644
--- a/src/com/android/launcher3/views/SearchSettingsRowView.java
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -29,9 +29,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
-import com.android.launcher3.allapps.AllAppsGridAdapter;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.launcher3.allapps.search.SearchEventTracker;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
@@ -41,14 +40,16 @@
  * A row of tappable TextViews with a breadcrumb for settings search.
  */
 public class SearchSettingsRowView extends LinearLayout implements
-        View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<Bundle> {
+        View.OnClickListener, AllAppsSearchBarController.SearchTargetHandler {
+
+    public static final String TARGET_TYPE_SETTINGS_ROW = "settings_row";
+
 
     private TextView mTitleView;
     private TextView mDescriptionView;
     private TextView mBreadcrumbsView;
     private Intent mIntent;
-    private AllAppsSearchPlugin mPlugin;
-    private final Object[] mTargetInfo = createTargetInfo();
+    private SearchTarget mSearchTarget;
 
 
     public SearchSettingsRowView(@NonNull Context context) {
@@ -75,10 +76,9 @@
     }
 
     @Override
-    public void applyAdapterInfo(
-            AllAppsGridAdapter.AdapterItemWithPayload<Bundle> adapterItemWithPayload) {
-        Bundle bundle = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
+    public void applySearchTarget(SearchTarget searchTarget) {
+        mSearchTarget = searchTarget;
+        Bundle bundle = searchTarget.getExtras();
         mIntent = bundle.getParcelable("intent");
         showIfAvailable(mTitleView, bundle.getString("title"));
         showIfAvailable(mDescriptionView, bundle.getString("description"));
@@ -86,12 +86,7 @@
         //TODO: implement RTL friendly breadcrumbs view
         showIfAvailable(mBreadcrumbsView, breadcrumbs != null
                 ? String.join(" > ", breadcrumbs) : null);
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
-    }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(searchTarget, this);
     }
 
     private void showIfAvailable(TextView view, @Nullable String string) {
@@ -108,19 +103,15 @@
         handleSelection(SearchTargetEvent.SELECT);
     }
 
-    private void handleSelection(int eventType) {
+    @Override
+    public void handleSelection(int eventType) {
         if (mIntent == null) return;
         // TODO: create ItemInfo object and then use it to call startActivityForResult for proper
         //  WW logging
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.startActivityForResult(mIntent, 0);
 
-        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
-                SearchTarget.ItemType.SETTINGS_ROW, eventType);
-        searchTargetEvent.bundle = new Bundle();
-        searchTargetEvent.bundle.putParcelable("intent", mIntent);
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(searchTargetEvent);
-        }
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
 }
diff --git a/src/com/android/launcher3/views/SearchSliceWrapper.java b/src/com/android/launcher3/views/SearchSliceWrapper.java
new file mode 100644
index 0000000..f8a7dc0
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchSliceWrapper.java
@@ -0,0 +1,82 @@
+/*
+ * 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 81bcad9..e929d7f 100644
--- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java
+++ b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
@@ -15,6 +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 android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
@@ -26,14 +29,13 @@
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
 import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.allapps.search.SearchEventTracker;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.RemoteActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Themes;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
@@ -41,11 +43,12 @@
  * A view representing a high confidence app search result that includes shortcuts
  */
 public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppCompatImageView
-        implements AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
+        implements AllAppsSearchBarController.SearchTargetHandler {
 
-    private final Object[] mTargetInfo = createTargetInfo();
-    AllAppsSearchPlugin mPlugin;
-    int mPosition;
+    public static final String TARGET_TYPE_SCREENSHOT = "screenshot";
+    public static final String TARGET_TYPE_SCREENSHOT_LEGACY = "screenshot_legacy";
+
+    private SearchTarget mSearchTarget;
 
     public ThumbnailSearchResultView(Context context) {
         super(context);
@@ -59,7 +62,8 @@
         super(context, attrs, defStyleAttr);
     }
 
-    private void handleSelection(int eventType) {
+    @Override
+    public void handleSelection(int eventType) {
         Launcher launcher = Launcher.getLauncher(getContext());
         ItemInfo itemInfo = (ItemInfo) getTag();
         if (itemInfo instanceof RemoteActionItemInfo) {
@@ -68,36 +72,30 @@
         } else {
             ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
         }
-        if (mPlugin != null) {
-            SearchTargetEvent event = getSearchTargetEvent(
-                    SearchTarget.ItemType.SCREENSHOT, eventType);
-            mPlugin.notifySearchTargetEvent(event);
-        }
+        SearchEventTracker.INSTANCE.get(getContext()).notifySearchTargetEvent(
+                new SearchTargetEvent.Builder(mSearchTarget, eventType).build());
     }
 
     @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItem) {
-        Launcher launcher = Launcher.getLauncher(getContext());
-        mPosition = adapterItem.position;
-
-        SearchTarget target = adapterItem.getPayload();
+    public void applySearchTarget(SearchTarget target) {
+        mSearchTarget = target;
         Bitmap bitmap;
-        if (target.mRemoteAction != null) {
-            RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.mRemoteAction,
-                    target.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
-                    target.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
-            bitmap = ((BitmapDrawable) target.mRemoteAction.getIcon()
-                .loadDrawable(getContext())).getBitmap();
-            Bitmap crop = Bitmap.createBitmap(bitmap, 0,
+        if (target.getRemoteAction() != null) {
+            RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(target.getRemoteAction(),
+                    target.getExtras().getString(REMOTE_ACTION_TOKEN),
+                    target.getExtras().getBoolean(REMOTE_ACTION_SHOULD_START));
+            bitmap = ((BitmapDrawable) target.getRemoteAction().getIcon()
+                    .loadDrawable(getContext())).getBitmap();
+            // crop
+            bitmap = Bitmap.createBitmap(bitmap, 0,
                     bitmap.getHeight() / 2 - bitmap.getWidth() / 2,
                     bitmap.getWidth(), bitmap.getWidth());
-            bitmap = crop;
             setTag(itemInfo);
         } else {
-            bitmap = (Bitmap) target.bundle.getParcelable("bitmap");
+            bitmap = (Bitmap) target.getExtras().getParcelable("bitmap");
             WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
             itemInfo.intent = new Intent(Intent.ACTION_VIEW)
-                    .setData(Uri.parse(target.bundle.getString("uri")))
+                    .setData(Uri.parse(target.getExtras().getString("uri")))
                     .setType("image/*")
                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             setTag(itemInfo);
@@ -106,12 +104,6 @@
         drawable.setCornerRadius(Themes.getDialogCornerRadius(getContext()));
         setImageDrawable(drawable);
         setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
-        mPlugin = adapterItem.getPlugin();
-        adapterItem.setSelectionHandler(this::handleSelection);
-    }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
+        SearchEventTracker.INSTANCE.get(getContext()).registerWeakHandler(target, this);
     }
 }
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
index aa3ab8f..5cc238d 100644
--- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -17,6 +17,8 @@
 package com.android.systemui.plugins;
 
 import android.app.Activity;
+import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.view.View;
 
 import com.android.systemui.plugins.annotations.ProvidesInterface;
@@ -32,7 +34,7 @@
 @ProvidesInterface(action = AllAppsSearchPlugin.ACTION, version = AllAppsSearchPlugin.VERSION)
 public interface AllAppsSearchPlugin extends Plugin {
     String ACTION = "com.android.systemui.action.PLUGIN_ALL_APPS_SEARCH_ACTIONS";
-    int VERSION = 7;
+    int VERSION = 8;
 
     void setup(Activity activity, View view);
 
@@ -49,10 +51,21 @@
     void onWindowVisibilityChanged(int visibility);
 
     /**
-     * Send signal when user starts typing, perform search, when search ends
+     * Send signal when user starts typing, perform search, notify search target
+     * event when search ends.
      */
     void startedSearchSession();
-    void performSearch(String query, Consumer<List<SearchTarget>> results);
+
+    /**
+     * Main function that triggers search.
+     *
+     * @param input string that has been typed by a user
+     * @param inputArgs extra info that may be relevant for the input query
+     * @param results contains the result that will be rendered in all apps search surface
+     * @param cancellationSignal {@link CancellationSignal} can be used to share status of current
+     */
+    void query(String input, Bundle inputArgs, Consumer<List<SearchTarget>> results,
+            CancellationSignal cancellationSignal);
 
     /**
      * Send over search target interaction events to Plugin
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
index 3f0dc39..2c7972e 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
@@ -16,8 +16,10 @@
 package com.android.systemui.plugins.shared;
 
 import android.app.RemoteAction;
+import android.content.ComponentName;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import java.util.List;
 
@@ -26,139 +28,145 @@
  */
 public class SearchTarget implements Comparable<SearchTarget> {
 
+    private final String mItemId;
+    private final String mItemType;
+    private final float mScore;
 
-    /**
-     * A bundle key for boolean value of whether remote action should be started in launcher or not
-     */
-    public static final String REMOTE_ACTION_SHOULD_START = "should_start_for_result";
-    public static final String REMOTE_ACTION_TOKEN = "action_token";
+    private final ComponentName mComponentName;
+    private final UserHandle mUserHandle;
+    private final List<ShortcutInfo> mShortcutInfos;
+    //TODO: (sfufa) replace with a list of a custom type
+    private final RemoteAction mRemoteAction;
+    private final Bundle mExtras;
 
-
-    public enum ViewType {
-
-        /**
-         * Consists of N number of icons. (N: launcher column count)
-         */
-        TOP_HIT(0),
-
-        /**
-         * Consists of 1 icon and two subsidiary icons.
-         */
-        HERO(1),
-
-        /**
-         * Main/sub/breadcrumb texts are rendered.
-         */
-        DETAIL(2),
-
-        /**
-         * Consists of an icon, three detail strings.
-         */
-        ROW(3),
-
-        /**
-         * Consists of an icon, three detail strings and a button.
-         */
-        ROW_WITH_BUTTON(4),
-
-        /**
-         * Consists of a single slice view
-         */
-        SLICE(5),
-
-        /**
-         * Similar to hero section.
-         */
-        SHORTCUT(6),
-
-        /**
-         * Person icon and handling app icons are rendered.
-         */
-        PEOPLE(7),
-
-        /**
-         * N number of 1x1 ratio thumbnail is rendered.
-         * (current N = 3)
-         */
-        THUMBNAIL(8),
-
-        /**
-         * Fallback search icon and relevant text is rendered.
-         */
-        SUGGEST(9);
-
-        private final int mId;
-
-        ViewType(int id) {
-            mId = id;
-        }
-
-        public int get() {
-            return mId;
-        }
+    private SearchTarget(String itemId, String itemType, float score,
+            ComponentName componentName, UserHandle userHandle, List<ShortcutInfo> shortcutInfos,
+            RemoteAction remoteAction, Bundle extras) {
+        mItemId = itemId;
+        mItemType = itemType;
+        mScore = score;
+        mComponentName = componentName;
+        mUserHandle = userHandle;
+        mShortcutInfos = shortcutInfos;
+        mExtras = extras;
+        mRemoteAction = remoteAction;
     }
 
-    public enum ItemType {
-        PLAY_RESULTS(0, "Play Store", ViewType.DETAIL),
-        SETTINGS_ROW(1, "Settings", ViewType.ROW),
-        SETTINGS_SLICE(2, "Settings", ViewType.SLICE),
-        APP(3, "", ViewType.TOP_HIT),
-        APP_HERO(4, "", ViewType.HERO),
-        SHORTCUT(5, "Shortcuts", ViewType.SHORTCUT),
-        PEOPLE(6, "People", ViewType.PEOPLE),
-        SCREENSHOT(7, "Screenshots", ViewType.THUMBNAIL),
-        ACTION(8, "Actions", ViewType.SHORTCUT),
-        SUGGEST(9, "Fallback Search", ViewType.SUGGEST),
-        CHROME_TAB(10, "Chrome Tab", ViewType.SHORTCUT);
-
-        private final int mId;
-
-        /** Used to render section title. */
-        private final String mTitle;
-        private final ViewType mViewType;
-
-        ItemType(int id, String title, ViewType type) {
-            mId = id;
-            mTitle = title;
-            mViewType = type;
-        }
-
-        public ViewType getViewType() {
-            return mViewType;
-        }
-
-        public String getTitle() {
-            return mTitle;
-        }
-
-        public int getId() {
-            return mId;
-        }
+    public String getItemId() {
+        return mItemId;
     }
 
-    public ItemType type;
-    public List<ShortcutInfo> shortcuts;
-    public Bundle bundle;
-    public float score;
-    public String mSessionId;
-    public RemoteAction mRemoteAction;
+    public String getItemType() {
+        return mItemType;
+    }
 
-    /**
-     * Constructor to create the search target. Bundle is currently temporary to hold
-     * search target primitives that cannot be expressed as java primitive objects
-     * or AOSP native objects.
-     */
-    public SearchTarget(ItemType itemType, List<ShortcutInfo> shortcuts,
-            Bundle bundle, float score, String sessionId) {
-        this.type = itemType;
-        this.shortcuts = shortcuts;
-        this.bundle = bundle;
-        this.score = score;
-        this.mSessionId = sessionId;
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    public float getScore() {
+        return mScore;
+    }
+
+    public List<ShortcutInfo> getShortcutInfos() {
+        return mShortcutInfos;
+    }
+
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    public RemoteAction getRemoteAction() {
+        return mRemoteAction;
     }
 
     @Override
     public int compareTo(SearchTarget o) {
-        return Float.compare(o.score, score);
+        return Float.compare(o.mScore, mScore);
+    }
+
+    /**
+     * A builder for {@link SearchTarget}
+     */
+    public static final class Builder {
+
+
+        private String mItemId;
+
+        private final String mItemType;
+        private final float mScore;
+
+
+        private ComponentName mComponentName;
+        private UserHandle mUserHandle;
+        private List<ShortcutInfo> mShortcutInfos;
+        private Bundle mExtras;
+        private RemoteAction mRemoteAction;
+
+        public Builder(String itemType, float score) {
+            this(itemType, score, null, null);
+        }
+
+        public Builder(String itemType, float score, ComponentName cn,
+                UserHandle user) {
+            mItemType = itemType;
+            mScore = score;
+            mComponentName = cn;
+            mUserHandle = user;
+        }
+
+        public String getItemId() {
+            return mItemId;
+        }
+
+        public float getScore() {
+            return mScore;
+        }
+
+        public Builder setItemId(String itemId) {
+            mItemId = itemId;
+            return this;
+        }
+
+        public Builder setComponentName(ComponentName componentName) {
+            mComponentName = componentName;
+            return this;
+        }
+
+        public Builder setUserHandle(UserHandle userHandle) {
+            mUserHandle = userHandle;
+            return this;
+        }
+
+        public Builder setShortcutInfos(List<ShortcutInfo> shortcutInfos) {
+            mShortcutInfos = shortcutInfos;
+            return this;
+        }
+
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public Builder setRemoteAction(RemoteAction remoteAction) {
+            mRemoteAction = remoteAction;
+            return this;
+        }
+
+        /**
+         * Builds a {@link SearchTarget}
+         */
+        public SearchTarget build() {
+            if (mItemId == null) {
+                throw new IllegalStateException("Item ID is required for building SearchTarget");
+            }
+            return new SearchTarget(mItemId, mItemType, mScore, mComponentName, mUserHandle,
+                    mShortcutInfos,
+                    mRemoteAction, mExtras);
+        }
     }
 }
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
index 5016abc..290fe54 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
@@ -15,32 +15,76 @@
  */
 package com.android.systemui.plugins.shared;
 
-import android.app.RemoteAction;
-import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 
 /**
  * Event used for the feedback loop to the plugin. (and future aiai)
  */
 public class SearchTargetEvent {
+    public static final int POSITION_NONE = -1;
+
     public static final int SELECT = 0;
     public static final int QUICK_SELECT = 1;
     public static final int LONG_PRESS = 2;
     public static final int CHILD_SELECT = 3;
 
-    public SearchTarget.ItemType type;
-    public ShortcutInfo shortcut;
-    public RemoteAction remoteAction;
-    public int eventType;
-    public Bundle bundle;
-    public int index;
-    public String sessionIdentifier;
+    private final SearchTarget mSearchTarget;
+    private final int mEventType;
+    private final int mShortcutPosition;
+    private final Bundle mExtras;
 
-    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType, int index,
-            String sessionId) {
-        this.type = itemType;
-        this.eventType = eventType;
-        this.index = index;
-        this.sessionIdentifier = sessionId;
+    public SearchTargetEvent(SearchTarget searchTarget, int eventType, int shortcutPosition,
+            Bundle extras) {
+        mSearchTarget = searchTarget;
+        mEventType = eventType;
+        mShortcutPosition = shortcutPosition;
+        mExtras = extras;
     }
+
+
+    public SearchTarget getSearchTarget() {
+        return mSearchTarget;
+    }
+
+    public int getShortcutPosition() {
+        return mShortcutPosition;
+    }
+
+    public int getEventType() {
+        return mEventType;
+    }
+
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * A builder for {@link SearchTarget}
+     */
+    public static final class Builder {
+        private final SearchTarget mSearchTarget;
+        private final int mEventType;
+        private int mShortcutPosition = POSITION_NONE;
+        private Bundle mExtras;
+
+        public Builder(SearchTarget searchTarget, int eventType) {
+            mSearchTarget = searchTarget;
+            mEventType = eventType;
+        }
+
+        public Builder setShortcutPosition(int shortcutPosition) {
+            mShortcutPosition = shortcutPosition;
+            return this;
+        }
+
+        public Builder setExtras(Bundle extras) {
+            mExtras = extras;
+            return this;
+        }
+
+        public SearchTargetEvent build() {
+            return new SearchTargetEvent(mSearchTarget, mEventType, mShortcutPosition, mExtras);
+        }
+    }
+
 }