Revert "Merge commit '8d14dbe041199d611839140f1c9285fd4174e9f4' ..."

Revert "Merging from ub-launcher3-master @ build 6877130"

Revert "Merging from ub-launcher3-master @ build 6877130"

Revert "Merging from ub-launcher3-master @ build 6877130"

Revert submission 12738409-merge_ub-launcher3-master_6877130

Reason for revert: Introduced crashes to global presubmit

Reverted Changes:
I624658ce6:Merge commit '8d14dbe041199d611839140f1c9285fd4174...
Iccd2f1e3a:Merging from ub-launcher3-master @ build 6877130
I791d64951:Merging from ub-launcher3-master @ build 6877130
Icdd32ab01:Merging from ub-launcher3-master @ build 6877130

Bug: 169963211
Change-Id: I77a4ae59e823147beae8dd7cb9b54ccdace2c7f4
(cherry picked from commit 087a9e39b663dade90d8dfd74300e42575649b87)
diff --git a/Android.mk b/Android.mk
index 349a134..25f5412 100644
--- a/Android.mk
+++ b/Android.mk
@@ -36,12 +36,18 @@
     launcher_log_protos_lite
 
 LOCAL_SRC_FILES := \
+    $(call all-proto-files-under, protos) \
+    $(call all-proto-files-under, proto_overrides) \
     $(call all-java-files-under, src_build_config) \
 
 LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
 
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/protos/ --proto_path=$(LOCAL_PATH)/proto_overrides/
+LOCAL_PROTO_JAVA_OUTPUT_PARAMS := enum_style=java
+
 LOCAL_SDK_VERSION := current
 LOCAL_MIN_SDK_VERSION := 26
 LOCAL_MODULE := Launcher3CommonDepsLib
@@ -129,6 +135,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-statsd \
     SystemUISharedLib \
+    launcherprotosnano \
     launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
@@ -196,6 +203,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     SystemUI-statsd \
     SystemUISharedLib \
+    launcherprotosnano \
     launcher_log_protos_lite
 ifneq (,$(wildcard frameworks/base))
   LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/build.gradle b/build.gradle
index 28a05d5..534ca65 100644
--- a/build.gradle
+++ b/build.gradle
@@ -82,6 +82,7 @@
             manifest.srcFile 'AndroidManifest-common.xml'
             proto {
                 srcDir 'protos/'
+                srcDir 'proto_overrides/'
             }
         }
 
@@ -149,6 +150,7 @@
     implementation "androidx.preference:preference:${ANDROID_X_VERSION}"
     implementation project(':IconLoader')
     withQuickstepImplementation project(':SharedLibWrapper')
+    implementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/libs", include: 'launcher_protos.jar')
 
     // Recents lib dependency
     withQuickstepImplementation fileTree(dir: "${FRAMEWORK_PREBUILTS_DIR}/quickstep/libs", include: 'sysui_shared.jar')
@@ -169,16 +171,20 @@
 protobuf {
     // Configure the protoc executable
     protoc {
-        artifact = "com.google.protobuf:protoc:${protocVersion}"
-    }
-    generateProtoTasks {
-        all().each { task ->
-            task.builtins {
-                remove java
-                java {
-                    option "lite"
+        artifact = 'com.google.protobuf:protoc:3.0.0'
+
+        generateProtoTasks {
+            all().each { task ->
+                task.builtins {
+                    remove java
+                    javanano {
+                        option "java_package=launcher_log_extension.proto|com.android.launcher3.userevent.nano"
+                        option "java_package=launcher_log.proto|com.android.launcher3.userevent.nano"
+                        option "java_package=launcher_dump.proto|com.android.launcher3.model.nano"
+                        option "enum_style=java"
+                    }
                 }
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/protos/launcher_log.proto b/protos/launcher_log.proto
new file mode 100644
index 0000000..9423cb2
--- /dev/null
+++ b/protos/launcher_log.proto
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+syntax = "proto2";
+
+import "launcher_log_extension.proto";
+
+option java_package = "com.android.launcher3.userevent";
+option java_outer_classname = "LauncherLogProto";
+
+package userevent;
+
+message Target {
+  enum Type {
+    NONE = 0;
+    ITEM = 1;
+    CONTROL = 2;
+    CONTAINER = 3;
+  }
+
+  optional Type type = 1;
+
+  // For container type and item type
+  // Used mainly for ContainerType.FOLDER, ItemType.*
+  optional int32 page_index = 2;
+  optional int32 rank = 3;
+  optional int32 grid_x = 4;
+  optional int32 grid_y = 5;
+
+  // For container types only
+  optional ContainerType container_type = 6;
+  optional int32 cardinality = 7;
+
+  // For control types only
+  optional ControlType control_type = 8;
+
+  // For item types only
+  optional ItemType item_type = 9;
+  optional int32 package_name_hash = 10;
+  optional int32 component_hash = 11;      // Used for ItemType.WIDGET
+  optional int32 intent_hash = 12;         // Used for ItemType.SHORTCUT
+  optional int32 span_x = 13 [default = 1];// Used for ItemType.WIDGET
+  optional int32 span_y = 14 [default = 1];// Used for ItemType.WIDGET
+  optional int32 predictedRank = 15;
+  optional TargetExtension extension = 16;
+  optional TipType tip_type = 17;
+  optional int32 search_query_length = 18;
+  optional bool is_work_app = 19;
+  optional FromFolderLabelState from_folder_label_state = 20;
+  optional ToFolderLabelState to_folder_label_state = 21;
+
+  // Note: proto does not support duplicate enum values, even if they belong to different enum type.
+  // Hence "FROM" and "TO" prefix added.
+  enum FromFolderLabelState {
+    FROM_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    FROM_EMPTY = 1;
+    FROM_CUSTOM = 2;
+    FROM_SUGGESTED = 3;
+  }
+
+  enum ToFolderLabelState {
+    TO_FOLDER_LABEL_STATE_UNSPECIFIED = 0;
+    TO_SUGGESTION0_WITH_VALID_PRIMARY = 1;
+    TO_SUGGESTION1_WITH_VALID_PRIMARY = 2;
+    TO_SUGGESTION1_WITH_EMPTY_PRIMARY = 3;
+    TO_SUGGESTION2_WITH_VALID_PRIMARY = 4;
+    TO_SUGGESTION2_WITH_EMPTY_PRIMARY = 5;
+    TO_SUGGESTION3_WITH_VALID_PRIMARY = 6;
+    TO_SUGGESTION3_WITH_EMPTY_PRIMARY = 7;
+    TO_EMPTY_WITH_VALID_SUGGESTIONS = 8 [deprecated = true];
+    TO_EMPTY_WITH_VALID_PRIMARY = 15;
+    TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 16;
+    TO_EMPTY_WITH_EMPTY_SUGGESTIONS = 9;
+    TO_EMPTY_WITH_SUGGESTIONS_DISABLED = 10;
+    TO_CUSTOM_WITH_VALID_SUGGESTIONS = 11 [deprecated = true];
+    TO_CUSTOM_WITH_VALID_PRIMARY = 17;
+    TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY = 18;
+    TO_CUSTOM_WITH_EMPTY_SUGGESTIONS = 12;
+    TO_CUSTOM_WITH_SUGGESTIONS_DISABLED = 13;
+    UNCHANGED = 14;
+  }
+}
+
+// Used to define what type of item a Target would represent.
+enum ItemType {
+  DEFAULT_ITEMTYPE = 0;
+  APP_ICON = 1;
+  SHORTCUT = 2;
+  WIDGET = 3;
+  FOLDER_ICON = 4;
+  DEEPSHORTCUT = 5;
+  SEARCHBOX = 6;
+  EDITTEXT = 7;
+  NOTIFICATION = 8;
+  TASK = 9;         // Each page of Recents UI (QuickStep)
+  WEB_APP = 10;
+  TASK_ICON = 11;
+}
+
+// Used to define what type of container a Target would represent.
+enum ContainerType {
+  DEFAULT_CONTAINERTYPE = 0;
+  WORKSPACE = 1;
+  HOTSEAT = 2;
+  FOLDER = 3;
+  ALLAPPS = 4;
+  WIDGETS = 5;
+  OVERVIEW = 6;   // Zoomed out workspace (without QuickStep)
+  PREDICTION = 7;
+  SEARCHRESULT = 8;
+  DEEPSHORTCUTS = 9;
+  PINITEM = 10;    // confirmation screen
+  NAVBAR = 11;
+  TASKSWITCHER = 12; // Recents UI Container (QuickStep)
+  APP = 13; // Foreground activity is another app (QuickStep)
+  TIP = 14; // Onboarding texts (QuickStep)
+  OTHER_LAUNCHER_APP = 15;
+}
+
+// Used to define what type of control a Target would represent.
+enum ControlType {
+  DEFAULT_CONTROLTYPE = 0;
+  ALL_APPS_BUTTON = 1;
+  WIDGETS_BUTTON = 2;
+  WALLPAPER_BUTTON = 3;
+  SETTINGS_BUTTON = 4;
+  REMOVE_TARGET = 5;
+  UNINSTALL_TARGET = 6;
+  APPINFO_TARGET = 7;
+  RESIZE_HANDLE = 8;
+  VERTICAL_SCROLL = 9;
+  HOME_INTENT = 10; // Deprecated, use enum Command instead
+  BACK_BUTTON = 11;
+  QUICK_SCRUB_BUTTON = 12;
+  CLEAR_ALL_BUTTON = 13;
+  CANCEL_TARGET = 14;
+  TASK_PREVIEW = 15;
+  SPLIT_SCREEN_TARGET = 16;
+  REMOTE_ACTION_SHORTCUT = 17;
+  APP_USAGE_SETTINGS = 18;
+  BACK_GESTURE = 19;
+  UNDO = 20;
+  DISMISS_PREDICTION = 21;
+  HYBRID_HOTSEAT_ACCEPTED = 22;
+  HYBRID_HOTSEAT_CANCELED = 23;
+  OVERVIEW_ACTIONS_SHARE_BUTTON = 24;
+  OVERVIEW_ACTIONS_SCREENSHOT_BUTTON = 25;
+  OVERVIEW_ACTIONS_SELECT_BUTTON = 26;
+  SELECT_MODE_CLOSE_BUTTON = 27;
+  SELECT_MODE_ITEM = 28;
+}
+
+enum TipType {
+  DEFAULT_NONE = 0;
+  BOUNCE = 1;
+  SWIPE_UP_TEXT = 2;
+  QUICK_SCRUB_TEXT = 3;
+  PREDICTION_TEXT = 4;
+  DWB_TOAST = 5;
+  HYBRID_HOTSEAT = 6;
+}
+
+// Used to define the action component of the LauncherEvent.
+message Action {
+  enum Type {
+    TOUCH = 0;
+    AUTOMATED = 1;
+    COMMAND = 2;
+    TIP = 3;
+    SOFT_KEYBOARD = 4;
+    // HARD_KEYBOARD, ASSIST
+  }
+
+  enum Touch {
+    TAP = 0;
+    LONGPRESS = 1;
+    DRAGDROP = 2;
+    SWIPE = 3;
+    FLING = 4;
+    PINCH = 5;
+    SWIPE_NOOP = 6;
+  }
+
+  enum Direction {
+    NONE = 0;
+    UP = 1;
+    DOWN = 2;
+    LEFT = 3;
+    RIGHT = 4;
+    UPRIGHT = 5;
+    UPLEFT = 6;
+  }
+  enum Command {
+    HOME_INTENT = 0;
+    BACK = 1;
+    ENTRY = 2;          // Indicates entry to one of Launcher container type target
+                        // not using the HOME_INTENT
+    CANCEL = 3;         // Indicates that a confirmation screen was cancelled
+    CONFIRM = 4;        // Indicates thata confirmation screen was accepted
+    STOP = 5;           // Indicates onStop() was called (screen time out, power off)
+    RECENTS_BUTTON = 6; // Indicates that Recents button was pressed
+    RESUME = 7;         // Indicates onResume() was called
+  }
+
+  optional Type type = 1;
+  optional Touch touch = 2;
+  optional Direction dir = 3;
+  optional Command command = 4;
+  // Log if the action was performed on outside of the container
+  optional bool is_outside = 5;
+  optional bool is_state_change = 6;
+}
+
+//
+// Context free grammar of typical user interaction:
+//         Action (Touch) + Target
+//         Action (Touch) + Target + Target
+//
+message LauncherEvent {
+  required Action action = 1;
+  // List of targets that touch actions can be operated on.
+  repeated Target src_target = 2;
+  repeated Target dest_target = 3;
+
+  optional int64 action_duration_millis = 4;
+  optional int64 elapsed_container_millis = 5;
+  optional int64 elapsed_session_millis = 6;
+
+  optional bool is_in_multi_window_mode = 7 [deprecated = true];
+  optional bool is_in_landscape_mode = 8 [deprecated = true];
+
+  optional LauncherEventExtension extension = 9;
+}
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 52475df..43bf0ea 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -13,8 +13,7 @@
     See the License for the specific language governing permissions and
     limitations under the License.
 -->
-<com.android.quickstep.interaction.RootSandboxLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="?android:attr/colorBackground">
@@ -94,11 +93,11 @@
         style="@style/TextAppearance.GestureTutorial.Feedback"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:layout_below="@id/gesture_tutorial_fragment_titles_container"
+        android:layout_above="@id/gesture_tutorial_fragment_action_button"
         android:layout_centerHorizontal="true"
         android:layout_marginStart="@dimen/gesture_tutorial_feedback_margin_start_end"
         android:layout_marginEnd="@dimen/gesture_tutorial_feedback_margin_start_end"
-        android:layout_marginTop="40dp"/>
+        android:layout_marginBottom="10dp"/>
 
     <!-- android:stateListAnimator="@null" removes shadow and normal on click behavior (increase
          of elevation and shadow) which is replaced by ripple effect in android:foreground -->
@@ -127,4 +126,4 @@
         android:background="@null"
         android:foreground="?android:attr/selectableItemBackgroundBorderless"
         android:stateListAnimator="@null"/>
-</com.android.quickstep.interaction.RootSandboxLayout>
\ No newline at end of file
+</RelativeLayout>
\ No newline at end of file
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 4b45b10..86120e3 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -149,21 +149,6 @@
     <!-- Feedback shown during interactive parts of Assistant gesture tutorial when the gesture doesn't go far enough. [CHAR LIMIT=100] -->
     <string name="assistant_gesture_feedback_swipe_not_long_enough" translatable="false">Try swiping further</string>
 
-    <!-- Title shown in sandbox mode part of gesture tutorial. [CHAR LIMIT=30] -->
-    <string name="sandbox_mode_title" translatable="false">Sandbox Mode</string>
-    <!-- Subtitle shown in sandbox mode part of gesture tutorial. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_subtitle" translatable="false">Try any navigation gesture</string>
-    <!-- Feedback shown in sandbox mode when the back gesture is successfully issued. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_back_gesture_feedback_successful" translatable="false">Back gesture successful</string>
-    <!-- Feedback shown in sandbox mode when the assistant gesture is a successfully issued. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_assistant_gesture_feedback_successful" translatable="false">Assistant gesture successful</string>
-    <!-- Feedback shown in sandbox mode when the home gesture is a successfully issued. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_home_gesture_feedback_successful" translatable="false">Home gesture successful</string>
-    <!-- Feedback shown in sandbox mode when the overview gesture is a successfully issued. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_overview_gesture_feedback_successful" translatable="false">Overview gesture successful</string>
-    <!-- Feedback shown in sandbox mode when the back gesture swipe is too far from the edge. [CHAR LIMIT=60] -->
-    <string name="sandbox_mode_back_gesture_feedback_swipe_too_far_from_edge" translatable="false">Make sure you swipe from the left/right edge of the screen</string>
-
     <!-- Title shown on the confirmation screen after successful gesture. [CHAR LIMIT=30] -->
     <string name="gesture_tutorial_confirm_title" translatable="false">All set</string>
     <!-- Button text shown on a button on the confirm screen to leave the tutorial. [CHAR LIMIT=14] -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 44d43c6..bc3b4ab 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -22,8 +22,6 @@
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
 
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
 import android.app.ActivityOptions;
@@ -321,10 +319,4 @@
     public void setHintUserWillBeActive() {
         addActivityFlags(ACTIVITY_STATE_USER_WILL_BE_ACTIVE);
     }
-
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        InteractionJankMonitorWrapper.init(getWindow().getDecorView());
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 5c8463c..b35b33c 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -34,7 +34,8 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 @TargetApi(Build.VERSION_CODES.P)
-public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
+public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat,
+        WrappedAnimationRunnerImpl {
 
     private static final String TAG = "LauncherAnimationRunner";
 
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index 057c0f4..b53e834 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -66,7 +66,6 @@
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -82,7 +81,6 @@
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat;
@@ -797,36 +795,6 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
-    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);
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    super.onAnimationCancel(animation);
-                    InteractionJankMonitorWrapper.cancel(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);
-                }
-            });
-        }
-    }
-
     /**
      * Remote animation runner for animation from the app to Launcher, including recents.
      */
@@ -891,9 +859,21 @@
                 // is initialized.
                 if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
                         || mLauncher.isForceInvisible()) {
-                    addCujInstrumentation(
-                            anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME,
-                            TRANSITION_OPEN_LAUNCHER);
+                    if (Trace.isEnabled()) {
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                Trace.beginAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+                                super.onAnimationStart(animation);
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                super.onAnimationEnd(animation);
+                                Trace.endAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
+                            }
+                        });
+                    }
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
 
@@ -962,12 +942,25 @@
                         launcherClosing);
             }
 
-            addCujInstrumentation(anim,
-                    launchingFromRecents
-                            ? InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS
-                            : InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON,
-                    launchingFromRecents
-                            ? TRANSITION_LAUNCH_FROM_RECENTS : TRANSITION_LAUNCH_FROM_ICON);
+            if (Trace.isEnabled()) {
+                final String section =
+                        launchingFromRecents
+                                ? TRANSITION_LAUNCH_FROM_RECENTS : TRANSITION_LAUNCH_FROM_ICON;
+
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        Trace.beginAsyncSection(section, 0);
+                        super.onAnimationStart(animation);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        Trace.endAsyncSection(section, 0);
+                    }
+                });
+            }
 
             if (launcherClosing) {
                 anim.addListener(mForceInvisibleListener);
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 5b066c6..d3c4c3d 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,8 +16,11 @@
 
 package com.android.launcher3.appprediction;
 
+import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
 
 import android.annotation.TargetApi;
 import android.content.Context;
@@ -40,6 +43,7 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsSectionDecorator;
 import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -49,11 +53,13 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.keyboard.FocusIndicatorHelper;
 import com.android.launcher3.keyboard.FocusIndicatorHelper.SimpleFocusIndicatorHelper;
+import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Themes;
 import com.android.quickstep.AnimatedFloat;
 
@@ -62,7 +68,7 @@
 
 @TargetApi(Build.VERSION_CODES.P)
 public class PredictionRowView extends LinearLayout implements
-        OnDeviceProfileChangeListener, FloatingHeaderRow {
+        LogContainerProvider, OnDeviceProfileChangeListener, FloatingHeaderRow {
 
     private static final IntProperty<PredictionRowView> TEXT_ALPHA =
             new IntProperty<PredictionRowView>("textAlpha") {
@@ -265,6 +271,29 @@
         mParent.onHeightUpdated();
     }
 
+    @Override
+    public void fillInLogContainerData(ItemInfo childInfo, LauncherLogProto.Target child,
+            ArrayList<LauncherLogProto.Target> parents) {
+        for (int i = 0; i < mPredictedApps.size(); i++) {
+            ItemInfoWithIcon appInfo = mPredictedApps.get(i);
+            if (appInfo == childInfo) {
+                child.predictedRank = i;
+                break;
+            }
+        }
+        parents.add(newContainerTarget(LauncherLogProto.ContainerType.PREDICTION));
+
+        // include where the prediction is coming this used to be Launcher#modifyUserEvent
+        LauncherLogProto.Target parent = newTarget(LauncherLogProto.Target.Type.CONTAINER);
+        LauncherState state = mLauncher.getStateManager().getState();
+        if (state == LauncherState.ALL_APPS) {
+            parent.containerType = LauncherLogProto.ContainerType.ALLAPPS;
+        } else if (state == OVERVIEW) {
+            parent.containerType = LauncherLogProto.ContainerType.TASKSWITCHER;
+        }
+        parents.add(parent);
+    }
+
     public void setTextAlpha(int textAlpha) {
         mIconLastSetTextAlpha = textAlpha;
         if (getAlpha() < 1 && textAlpha > 0) {
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 3807350..151a113 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -55,6 +55,7 @@
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.Snackbar;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index a2e3bdf..bce73cd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
 import android.content.Context;
@@ -24,6 +23,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Definition for AllApps state
@@ -40,7 +40,7 @@
     };
 
     public AllAppsState(int id) {
-        super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
+        super(id, ContainerType.ALLAPPS, STATE_FLAGS);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 4b4f955..8ff05f2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -15,13 +15,12 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-
 import android.content.Context;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.allapps.AllAppsTransitionController;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
 
@@ -34,7 +33,7 @@
             | FLAG_WORKSPACE_INACCESSIBLE | FLAG_NON_INTERACTIVE | FLAG_CLOSE_POPUPS;
 
     public BackgroundAppState(int id) {
-        this(id, LAUNCHER_STATE_BACKGROUND);
+        this(id, LauncherLogProto.ContainerType.TASKSWITCHER);
     }
 
     protected BackgroundAppState(int id, int logContainer) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 41c689d..fc0dcd5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -15,14 +15,13 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-
 import android.content.Context;
 import android.graphics.Rect;
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -35,7 +34,7 @@
             FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_UI | FLAG_WORKSPACE_INACCESSIBLE;
 
     public OverviewModalTaskState(int id) {
-        super(id, LAUNCHER_STATE_OVERVIEW, STATE_FLAGS);
+        super(id, ContainerType.OVERVIEW, STATE_FLAGS);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 525ff58..bbe7821 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.hideShelfInTwoButtonLandscape;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
@@ -30,6 +29,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -51,7 +51,7 @@
     }
 
     protected OverviewState(int id, int stateFlags) {
-        this(id, LAUNCHER_STATE_OVERVIEW, stateFlags);
+        this(id, ContainerType.TASKSWITCHER, stateFlags);
     }
 
     protected OverviewState(int id, int logContainer, int stateFlags) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 51e72da..2c7373e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -15,9 +15,8 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-
 import com.android.launcher3.Launcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 /**
  * State to indicate we are about to launch a recent task. Note that this state is only used when
@@ -27,7 +26,7 @@
 public class QuickSwitchState extends BackgroundAppState {
 
     public QuickSwitchState(int id) {
-        super(id, LAUNCHER_STATE_BACKGROUND);
+        super(id, LauncherLogProto.ContainerType.APP);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 7a0f634..bef191e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -12,6 +12,8 @@
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.quickstep.SystemUiProxy;
 
 /**
@@ -44,6 +46,11 @@
     }
 
     @Override
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return LauncherLogProto.ContainerType.NAVBAR;
+    }
+
+    @Override
     protected float getShiftRange() {
         return mLauncher.getDragLayer().getWidth();
     }
@@ -58,8 +65,13 @@
     }
 
     @Override
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
-        super.onSwipeInteractionCompleted(targetState);
+    protected int getDirectionForLog() {
+        return mLauncher.getDeviceProfile().isSeascape() ? Direction.RIGHT : Direction.LEFT;
+    }
+
+    @Override
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
             SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index d210bc6..7616844 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,9 +45,12 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.AssistantUtilities;
@@ -208,6 +211,7 @@
     @Override
     public void onDragEnd(float velocity) {
         boolean fling = mSwipeDetector.isFling(velocity);
+        final int logAction = fling ? Touch.FLING : Touch.SWIPE;
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = PULLBACK_INTERPOLATOR.getInterpolation(progress);
         boolean success = interpolatedProgress >= SUCCESS_TRANSITION_PROGRESS
@@ -226,7 +230,7 @@
                         () -> onSwipeInteractionCompleted(mEndState));
             }
             if (mStartState != mEndState) {
-                logHomeGesture();
+                // TODO: add to WW log
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
             if (topOpenView != null) {
@@ -251,10 +255,17 @@
         AccessibilityManagerCompat.sendStateEventToTest(mLauncher, targetState.ordinal);
     }
 
-    private void logHomeGesture() {
+    private void logStateChange(int startContainerType, int logAction) {
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                LauncherLogProto.Action.Direction.UP,
+                mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
+                LauncherLogProto.ContainerType.NAVBAR,
+                startContainerType,
+                mEndState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
         mLauncher.getStatsLogManager().logger()
-                .withSrcState(mStartState.statsLogOrdinal)
-                .withDstState(mEndState.statsLogOrdinal)
+                .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType))
+                .withDstState(StatsLogManager.containerTypeToAtomState(mEndState.containerType))
                 .log(LAUNCHER_HOME_GESTURE);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 2a3bdbf..c78d474 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -49,6 +49,7 @@
 import com.android.launcher3.graphics.OverviewScrim;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -206,7 +207,7 @@
 
     private void maybeSwipeInteractionToOverviewComplete() {
         if (mReachedOverview && mDetector.isSettlingState()) {
-            onSwipeInteractionCompleted(OVERVIEW);
+            onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
         }
     }
 
@@ -250,7 +251,7 @@
     private void goToOverviewOrHomeOnDragEnd(float velocity) {
         boolean goToHomeInsteadOfOverview = !mMotionPauseDetector.isPaused();
         if (goToHomeInsteadOfOverview) {
-            new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL))
+            new OverviewToHomeAnim(mLauncher, ()-> onSwipeInteractionCompleted(NORMAL, Touch.FLING))
                     .animateWithVelocity(velocity);
         }
         if (mReachedOverview) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index f378848..a6a3497 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -57,9 +57,13 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.BothAxesSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.AnimatedFloat;
@@ -283,6 +287,7 @@
         boolean horizontalFling = mSwipeDetector.isFling(velocity.x);
         boolean verticalFling = mSwipeDetector.isFling(velocity.y);
         boolean noFling = !horizontalFling && !verticalFling;
+        int logAction = noFling ? Touch.SWIPE : Touch.FLING;
         if (mMotionPauseDetector.isPaused() && noFling) {
             cancelAnimations();
 
@@ -293,7 +298,7 @@
             overviewAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    onAnimationToStateCompleted(OVERVIEW);
+                    onAnimationToStateCompleted(OVERVIEW, logAction);
                 }
             });
             overviewAnim.start();
@@ -388,7 +393,7 @@
         }
 
         nonOverviewAnim.setDuration(Math.max(xDuration, yDuration));
-        mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState));
+        mNonOverviewAnim.setEndAction(() -> onAnimationToStateCompleted(targetState, logAction));
 
         cancelAnimations();
         xOverviewAnim.start();
@@ -396,17 +401,27 @@
         nonOverviewAnim.start();
     }
 
-    private void onAnimationToStateCompleted(LauncherState targetState) {
+    private void onAnimationToStateCompleted(LauncherState targetState, int logAction) {
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                getDirectionForLog(), mSwipeDetector.getDownX(), mSwipeDetector.getDownY(),
+                LauncherLogProto.ContainerType.NAVBAR,
+                mStartState.containerType,
+                targetState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
         mLauncher.getStatsLogManager().logger()
                 .withSrcState(LAUNCHER_STATE_HOME)
-                .withDstState(targetState.statsLogOrdinal)
-                .log(getLauncherAtomEvent(mStartState.statsLogOrdinal, targetState.statsLogOrdinal,
+                .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType))
+                .log(getLauncherAtomEvent(mStartState.containerType, targetState.containerType,
                         targetState.ordinal > mStartState.ordinal
                                 ? LAUNCHER_UNKNOWN_SWIPEUP
                                 : LAUNCHER_UNKNOWN_SWIPEDOWN));
         mLauncher.getStateManager().goToState(targetState, false, this::clearState);
     }
 
+    private int getDirectionForLog() {
+        return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+    }
+
     private void cancelAnimations() {
         if (mNonOverviewAnim != null) {
             mNonOverviewAnim.getAnimationPlayer().cancel();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
index 45e5e2f..9091168 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/OverviewToAllAppsTouchController.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.views.RecentsView;
 
@@ -72,4 +73,9 @@
         }
         return fromState;
     }
+
+    @Override
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return LauncherLogProto.ContainerType.WORKSPACE;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 037d988..059a703 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -48,6 +48,8 @@
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TouchInteractionService;
 import com.android.quickstep.util.LayoutUtils;
@@ -170,6 +172,11 @@
         return fromState;
     }
 
+    @Override
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+    }
+
     private StateAnimationConfig getNormalToOverviewAnimation() {
         mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
 
@@ -278,7 +285,7 @@
 
     private void cancelPendingAnim() {
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.finish(false, Touch.SWIPE);
             mPendingAnimation = null;
         }
     }
@@ -313,8 +320,8 @@
     }
 
     @Override
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
-        super.onSwipeInteractionCompleted(targetState);
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
             SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index b8c2030..c643858 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -42,6 +41,8 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SystemUiProxy;
@@ -91,14 +92,14 @@
     @Override
     public void onDragStart(boolean start, float startDisplacement) {
         super.onDragStart(start, startDisplacement);
-        mStartContainerType = LAUNCHER_STATE_BACKGROUND;
+        mStartContainerType = LauncherLogProto.ContainerType.NAVBAR;
         ActivityManagerWrapper.getInstance()
                 .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS);
     }
 
     @Override
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
-        super.onSwipeInteractionCompleted(targetState);
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
+        super.onSwipeInteractionCompleted(targetState, logAction);
     }
 
     @Override
@@ -152,4 +153,14 @@
     protected float getShiftRange() {
         return mLauncher.getDeviceProfile().widthPx / 2f;
     }
+
+    @Override
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return LauncherLogProto.ContainerType.NAVBAR;
+    }
+
+    @Override
+    protected int getDirectionForLog() {
+        return Utilities.isRtl(mLauncher.getResources()) ? Direction.LEFT : Direction.RIGHT;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
index fe69c9b..16bd9ed 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/StatusBarTouchController.java
@@ -15,12 +15,10 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN;
+import static android.view.MotionEvent.ACTION_CANCEL;
 
 import android.graphics.PointF;
 import android.util.SparseArray;
@@ -33,9 +31,12 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
-import com.android.quickstep.SystemUiProxy;
 
+import com.android.quickstep.SystemUiProxy;
 import java.io.PrintWriter;
 
 /**
@@ -132,8 +133,9 @@
         int action = ev.getAction();
         if (action == ACTION_UP || action == ACTION_CANCEL) {
             dispatchTouchEvent(ev);
-            mLauncher.getStatsLogManager().logger()
-                    .log(LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN);
+            mLauncher.getUserEventDispatcher().logActionOnContainer(action == ACTION_UP ?
+                    Touch.FLING : Touch.SWIPE, Direction.DOWN, ContainerType.WORKSPACE,
+                    mLauncher.getWorkspace().getCurrentPage());
             setWindowSlippery(false);
             return true;
         }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 186caf6..df6194d 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
@@ -202,7 +203,7 @@
             mCurrentAnimation.setPlayFraction(0);
         }
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.finish(false, Touch.SWIPE);
             mPendingAnimation = null;
         }
 
@@ -284,6 +285,7 @@
     public void onDragEnd(float velocity) {
         boolean fling = mDetector.isFling(velocity);
         final boolean goingToEnd;
+        final int logAction;
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
         if (blockedFling) {
             fling = false;
@@ -292,9 +294,11 @@
         float progress = mCurrentAnimation.getProgressFraction();
         float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
+            logAction = Touch.FLING;
             boolean goingUp = orientationHandler.isGoingUp(velocity, mIsRtl);
             goingToEnd = goingUp == mCurrentAnimationIsGoingUp;
         } else {
+            logAction = Touch.SWIPE;
             goingToEnd = interpolatedProgress > SUCCESS_TRANSITION_PROGRESS;
         }
         long animationDuration = BaseSwipeDetector.calculateDuration(
@@ -303,14 +307,14 @@
             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
         }
 
-        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd, logAction));
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity, mEndDisplacement, animationDuration);
     }
 
-    private void onCurrentAnimationEnd(boolean wasSuccess) {
+    private void onCurrentAnimationEnd(boolean wasSuccess, int logAction) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(wasSuccess);
+            mPendingAnimation.finish(wasSuccess, logAction);
             mPendingAnimation = null;
         }
         clearState();
@@ -322,7 +326,7 @@
         mTaskBeingDragged = null;
         mCurrentAnimation = null;
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.finish(false, Touch.SWIPE);
             mPendingAnimation = null;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 5e05a7d..26ad377 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -75,8 +75,12 @@
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
@@ -97,7 +101,6 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InputConsumerController;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.LatencyTrackerCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TaskInfoCompat;
@@ -216,7 +219,8 @@
 
     private boolean mPassedOverviewThreshold;
     private boolean mGestureStarted;
-    private boolean mLogDirectionUpOrLeft = true;
+    private int mLogAction = Touch.SWIPE;
+    private int mLogDirection = Direction.UP;
     private PointF mDownPos;
     private boolean mIsLikelyToStartNewTask;
 
@@ -430,13 +434,9 @@
                 mOnDeferredActivityLaunch);
 
         mGestureState.runOnceAtState(STATE_END_TARGET_SET,
-                () -> {
-                    mDeviceState.getRotationTouchHelper()
-                            .onEndTargetCalculated(mGestureState.getEndTarget(),
-                                    mActivityInterface);
-
-                    mRecentsView.onGestureEndTargetCalculated(mGestureState.getEndTarget());
-                });
+                () -> mDeviceState.getRotationTouchHelper().
+                        onEndTargetCalculated(mGestureState.getEndTarget(),
+                                mActivityInterface));
 
         notifyGestureStartedAsync();
     }
@@ -741,6 +741,7 @@
     public void onGestureCancelled() {
         updateDisplacement(0);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
+        mLogAction = Touch.SWIPE_NOOP;
         handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
     }
 
@@ -756,11 +757,13 @@
         boolean isFling = mGestureStarted && !mIsMotionPaused
                 && Math.abs(endVelocity) > flingThreshold;
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
+
+        mLogAction = isFling ? Touch.FLING : Touch.SWIPE;
         boolean isVelocityVertical = Math.abs(velocity.y) > Math.abs(velocity.x);
         if (isVelocityVertical) {
-            mLogDirectionUpOrLeft = velocity.y < 0;
+            mLogDirection = velocity.y < 0 ? Direction.UP : Direction.DOWN;
         } else {
-            mLogDirectionUpOrLeft = velocity.x < 0;
+            mLogDirection = velocity.x < 0 ? Direction.LEFT : Direction.RIGHT;
         }
         mDownPos = downPos;
         handleNormalGestureEnd(endVelocity, isFling, velocity, false /* isCancel */);
@@ -972,7 +975,8 @@
                 break;
             case LAST_TASK:
             case NEW_TASK:
-                event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
+                event = (mLogDirection == Direction.LEFT)
+                        ? LAUNCHER_QUICKSWITCH_LEFT
                         : LAUNCHER_QUICKSWITCH_RIGHT;
                 break;
             default:
@@ -980,10 +984,12 @@
         }
         StatsLogger logger = StatsLogManager.newInstance(mContext).logger()
                 .withSrcState(LAUNCHER_STATE_BACKGROUND)
-                .withDstState(endTarget.containerType);
+                .withDstState(StatsLogManager.containerTypeToAtomState(endTarget.containerType));
         if (targetTask != null) {
             logger.withItemInfo(targetTask.getItemInfo());
         }
+        logger.log(event);
+
 
         DeviceProfile dp = mDp;
         if (dp == null || mDownPos == null) {
@@ -993,8 +999,12 @@
         int pageIndex = endTarget == LAST_TASK
                 ? LOG_NO_OP_PAGE_INDEX
                 : mRecentsView.getNextPage();
-        // TODO: set correct container using the pageIndex
-        logger.log(event);
+        UserEventDispatcher.newInstance(mContext).logStateChangeAction(
+                mLogAction, mLogDirection,
+                (int) mDownPos.x, (int) mDownPos.y,
+                ContainerType.NAVBAR, ContainerType.APP,
+                endTarget.containerType,
+                pageIndex);
     }
 
     /** Animates to the given progress, where 0 is the current app and 1 is overview. */
@@ -1131,12 +1141,10 @@
         anim.addOnUpdateListener((r, p) -> {
             updateSysUiFlags(Math.max(p, mCurrentShift.value));
         });
-        final int cuj = InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME;
         anim.addAnimatorListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
                 Trace.beginAsyncSection(TRANSITION_OPEN_LAUNCHER, 0);
-                InteractionJankMonitorWrapper.begin(cuj);
                 if (mActivity != null) {
                     removeLiveTileOverlay();
                 }
@@ -1150,13 +1158,6 @@
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
                 mActivityInterface.onSwipeUpToHomeComplete(mDeviceState);
-                InteractionJankMonitorWrapper.end(cuj);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                super.onAnimationCancel(animation);
-                InteractionJankMonitorWrapper.cancel(cuj);
             }
 
             @Override
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index d35e270..8b108ac 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -152,6 +152,11 @@
     public abstract void onExitOverview(RotationTouchHelper deviceState,
             Runnable exitRunnable);
 
+    /**
+     * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
+     */
+    public abstract int getContainerType();
+
     public abstract boolean isInLiveTileMode();
 
     public abstract void onLaunchTaskFailed();
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 2885abf..3898f0b 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -28,6 +28,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -144,6 +145,15 @@
     }
 
     @Override
+    public int getContainerType() {
+        RecentsActivity activity = getCreatedActivity();
+        boolean visible = activity != null && activity.isStarted() && activity.hasWindowFocus();
+        return visible
+                ? LauncherLogProto.ContainerType.OTHER_LAUNCHER_APP
+                : LauncherLogProto.ContainerType.APP;
+    }
+
+    @Override
     public boolean isInLiveTileMode() {
         return false;
     }
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 9c23c83..00b5eb9 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -15,9 +15,6 @@
  */
 package com.android.quickstep;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 
 import android.annotation.TargetApi;
@@ -26,6 +23,7 @@
 import android.os.Build;
 
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
@@ -46,13 +44,13 @@
      * Defines the end targets of a gesture and the associated state.
      */
     public enum GestureEndTarget {
-        HOME(true, LAUNCHER_STATE_HOME, false),
+        HOME(true, ContainerType.WORKSPACE, false),
 
-        RECENTS(true, LAUNCHER_STATE_OVERVIEW, true),
+        RECENTS(true, ContainerType.TASKSWITCHER, true),
 
-        NEW_TASK(false, LAUNCHER_STATE_BACKGROUND, true),
+        NEW_TASK(false, ContainerType.APP, true),
 
-        LAST_TASK(false, LAUNCHER_STATE_BACKGROUND, true);
+        LAST_TASK(false, ContainerType.APP, true);
 
         GestureEndTarget(boolean isLauncher, int containerType,
                 boolean recentsAttachedToAppWindow) {
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 98b8455..036d473 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -44,6 +44,7 @@
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -207,6 +208,10 @@
             return false;
         }
 
+        launcher.getUserEventDispatcher().logActionCommand(
+                LauncherLogProto.Action.Command.RECENTS_BUTTON,
+                getContainerType(),
+                LauncherLogProto.ContainerType.TASKSWITCHER);
         launcher.getStateManager().goToState(OVERVIEW,
                 launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
         return true;
@@ -248,6 +253,13 @@
     }
 
     @Override
+    public int getContainerType() {
+        final Launcher launcher = getVisibleLauncher();
+        return launcher != null ? launcher.getStateManager().getState().containerType
+                : LauncherLogProto.ContainerType.APP;
+    }
+
+    @Override
     public boolean isInLiveTileMode() {
         Launcher launcher = getCreatedActivity();
         return launcher != null && launcher.getStateManager().getState() == OVERVIEW &&
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index b1b9396..365cff5 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -30,6 +30,7 @@
 import androidx.annotation.BinderThread;
 
 import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.views.RecentsView;
@@ -46,6 +47,7 @@
 
     private final Context mContext;
     private final RecentsAnimationDeviceState mDeviceState;
+    private final RecentsModel mRecentsModel;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private long mLastToggleTime;
@@ -54,6 +56,7 @@
             OverviewComponentObserver observer) {
         mContext = context;
         mDeviceState = deviceState;
+        mRecentsModel = RecentsModel.INSTANCE.get(mContext);
         mOverviewComponentObserver = observer;
     }
 
@@ -147,6 +150,7 @@
         private final AppToOverviewAnimationProvider<T> mAnimationProvider;
 
         private final long mToggleClickedTime = SystemClock.uptimeMillis();
+        private boolean mUserEventLogged;
         private ActivityInitListener mListener;
 
         public RecentsActivityCommand() {
@@ -156,7 +160,7 @@
                     ActivityManagerWrapper.getInstance().getRunningTask(), mDeviceState);
 
             // Preload the plan
-            RecentsModel.INSTANCE.get(mContext).getTasks(null);
+            mRecentsModel.getTasks(null);
         }
 
         @Override
@@ -208,6 +212,13 @@
 
         private boolean onActivityReady(Boolean wasVisible) {
             final T activity = mActivityInterface.getCreatedActivity();
+            if (!mUserEventLogged) {
+                activity.getUserEventDispatcher().logActionCommand(
+                        LauncherLogProto.Action.Command.RECENTS_BUTTON,
+                        mActivityInterface.getContainerType(),
+                        LauncherLogProto.ContainerType.TASKSWITCHER);
+                mUserEventLogged = true;
+            }
             return mAnimationProvider.onActivityReady(activity, wasVisible);
         }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 2f55f14..6f2f9fb 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -41,10 +41,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
 import com.android.launcher3.R;
-import com.android.launcher3.WrappedAnimationRunnerImpl;
-import com.android.launcher3.WrappedLauncherAnimationRunner;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
@@ -90,9 +87,6 @@
 
     private StateManager<RecentsState> mStateManager;
 
-    // Strong refs to runners which are cleared when the activity is destroyed
-    private WrappedAnimationRunnerImpl mActivityLaunchAnimationRunner;
-
     /**
      * Init drag layer and overview panel views.
      */
@@ -124,6 +118,7 @@
          * etc.)
          */
     protected void onHandleConfigChanged() {
+        mUserEventDispatcher = null;
         initDeviceProfile();
 
         AbstractFloatingView.closeOpenViews(this, true,
@@ -175,11 +170,8 @@
         }
 
         final TaskView taskView = (TaskView) v;
-        mActivityLaunchAnimationRunner = new WrappedAnimationRunnerImpl() {
-            @Override
-            public Handler getHandler() {
-                return mUiHandler;
-            }
+        RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mUiHandler,
+                true /* startAtFrontOfQueue */) {
 
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
@@ -190,10 +182,8 @@
                 result.setAnimation(anim, RecentsActivity.this);
             }
         };
-        final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner<>(
-                mActivityLaunchAnimationRunner, true /* startAtFrontOfQueue */);
         return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat(
-                wrapper, RECENTS_LAUNCH_DURATION,
+                runner, RECENTS_LAUNCH_DURATION,
                 RECENTS_LAUNCH_DURATION - STATUS_BAR_TRANSITION_DURATION
                         - STATUS_BAR_TRANSITION_PRE_DELAY));
     }
@@ -298,7 +288,6 @@
     protected void onDestroy() {
         super.onDestroy();
         ACTIVITY_TRACKER.onActivityDestroyed(this);
-        mActivityLaunchAnimationRunner = null;
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d47217b..6c302ae 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -18,6 +18,7 @@
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.createAndStartNewLooper;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
 import android.annotation.TargetApi;
@@ -25,11 +26,11 @@
 import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.os.Build;
+import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
 
 import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -39,8 +40,6 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
 import java.util.function.Consumer;
 
 /**
@@ -53,9 +52,6 @@
     public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
             new MainThreadInitializedObject<>(RecentsModel::new);
 
-    private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
-            new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
-
     private final List<TaskVisualsChangeListener> mThumbnailChangeListeners = new ArrayList<>();
     private final Context mContext;
 
@@ -65,10 +61,12 @@
 
     private RecentsModel(Context context) {
         mContext = context;
+        Looper looper =
+                createAndStartNewLooper("TaskThumbnailIconCache", THREAD_PRIORITY_BACKGROUND);
         mTaskList = new RecentTasksList(MAIN_EXECUTOR,
                 new KeyguardManagerCompat(context), ActivityManagerWrapper.getInstance());
-        mIconCache = new TaskIconCache(context, RECENTS_MODEL_EXECUTOR);
-        mThumbnailCache = new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR);
+        mIconCache = new TaskIconCache(context, looper);
+        mThumbnailCache = new TaskThumbnailCache(context, looper);
 
         ActivityManagerWrapper.getInstance().registerTaskStackListener(this);
         IconProvider.registerIconChangeListener(context,
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 65e89cf..7ff799e 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.FastBitmapDrawable.newIcon;
 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
@@ -26,6 +27,8 @@
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.util.SparseArray;
 import android.view.accessibility.AccessibilityManager;
@@ -34,11 +37,12 @@
 
 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;
+import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -46,7 +50,6 @@
 import com.android.systemui.shared.system.PackageManagerWrapper;
 import com.android.systemui.shared.system.TaskDescriptionCompat;
 
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 /**
@@ -54,7 +57,7 @@
  */
 public class TaskIconCache {
 
-    private final Executor mBgExecutor;
+    private final Handler mBackgroundHandler;
     private final AccessibilityManager mAccessibilityManager;
 
     private final Context mContext;
@@ -62,9 +65,9 @@
     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
     private final IconProvider mIconProvider;
 
-    public TaskIconCache(Context context, Executor bgExecutor) {
+    public TaskIconCache(Context context, Looper backgroundLooper) {
         mContext = context;
-        mBgExecutor = bgExecutor;
+        mBackgroundHandler = new Handler(backgroundLooper);
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
 
         Resources res = context.getResources();
@@ -80,27 +83,31 @@
      * @param callback The callback to receive the task after its data has been populated.
      * @return A cancelable handle to the request
      */
-    public CancellableTask updateIconInBackground(Task task, Consumer<Task> callback) {
+    public IconLoadRequest updateIconInBackground(Task task, Consumer<Task> callback) {
         Preconditions.assertUIThread();
         if (task.icon != null) {
             // Nothing to load, the icon is already loaded
             callback.accept(task);
             return null;
         }
-        CancellableTask<TaskCacheEntry> request = new CancellableTask<TaskCacheEntry>() {
-            @Override
-            public TaskCacheEntry getResultOnBg() {
-                return getCacheEntry(task);
-            }
 
+        IconLoadRequest request = new IconLoadRequest(mBackgroundHandler) {
             @Override
-            public void handleResult(TaskCacheEntry result) {
-                task.icon = result.icon;
-                task.titleDescription = result.contentDescription;
-                callback.accept(task);
+            public void run() {
+                TaskCacheEntry entry = getCacheEntry(task);
+                if (isCanceled()) {
+                    // We don't call back to the provided callback in this case
+                    return;
+                }
+                MAIN_EXECUTOR.execute(() -> {
+                    task.icon = entry.icon;
+                    task.titleDescription = entry.contentDescription;
+                    callback.accept(task);
+                    onEnd();
+                });
             }
         };
-        mBgExecutor.execute(request);
+        Utilities.postAsyncCallback(mBackgroundHandler, request);
         return request;
     }
 
@@ -113,8 +120,9 @@
     }
 
     void invalidateCacheEntries(String pkg, UserHandle handle) {
-        mBgExecutor.execute(() -> mIconCache.removeAll(key ->
-                pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
+        Utilities.postAsyncCallback(mBackgroundHandler,
+                () -> mIconCache.removeAll(key ->
+                        pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
     }
 
     @WorkerThread
@@ -200,6 +208,12 @@
         }
     }
 
+    public static abstract class IconLoadRequest extends HandlerRunnable {
+        IconLoadRequest(Handler handler) {
+            super(handler, null);
+        }
+    }
+
     private static class TaskCacheEntry {
         public Drawable icon;
         public String contentDescription = "";
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 65bb0f3..2b55b80 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_SELECTIONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_SPLIT_SCREEN_TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -38,6 +39,7 @@
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.quickstep.views.RecentsView;
@@ -226,6 +228,8 @@
 
         @Override
         protected boolean onActivityStarted(BaseDraggingActivity activity) {
+            activity.getUserEventDispatcher().logActionOnControl(TAP,
+                    LauncherLogProto.ControlType.SPLIT_SCREEN_TARGET);
             return true;
         }
     };
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
index a8a0219..2b7a8ec 100644
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
@@ -15,12 +15,17 @@
  */
 package com.android.quickstep;
 
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.Handler;
+import android.os.Looper;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.cache.HandlerRunnable;
 import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.TaskKeyLruCache;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
@@ -28,12 +33,11 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 public class TaskThumbnailCache {
 
-    private final Executor mBgExecutor;
+    private final Handler mBackgroundHandler;
 
     private final int mCacheSize;
     private final TaskKeyLruCache<ThumbnailData> mCache;
@@ -90,8 +94,8 @@
         }
     }
 
-    public TaskThumbnailCache(Context context, Executor bgExecutor) {
-        mBgExecutor = bgExecutor;
+    public TaskThumbnailCache(Context context, Looper backgroundLooper) {
+        mBackgroundHandler = new Handler(backgroundLooper);
         mHighResLoadingState = new HighResLoadingState(context);
 
         Resources res = context.getResources();
@@ -126,7 +130,7 @@
      * @param callback The callback to receive the task after its data has been populated.
      * @return A cancelable handle to the request
      */
-    public CancellableTask updateThumbnailInBackground(
+    public ThumbnailLoadRequest updateThumbnailInBackground(
             Task task, Consumer<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
@@ -138,13 +142,14 @@
             return null;
         }
 
+
         return updateThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), t -> {
             task.thumbnail = t;
             callback.accept(t);
         });
     }
 
-    private CancellableTask updateThumbnailInBackground(TaskKey key, boolean lowResolution,
+    private ThumbnailLoadRequest updateThumbnailInBackground(TaskKey key, boolean lowResolution,
             Consumer<ThumbnailData> callback) {
         Preconditions.assertUIThread();
 
@@ -155,20 +160,26 @@
             return null;
         }
 
-        CancellableTask<ThumbnailData> request = new CancellableTask<ThumbnailData>() {
+        ThumbnailLoadRequest request = new ThumbnailLoadRequest(mBackgroundHandler,
+                lowResolution) {
             @Override
-            public ThumbnailData getResultOnBg() {
-                return ActivityManagerWrapper.getInstance().getTaskThumbnail(
+            public void run() {
+                ThumbnailData thumbnail = ActivityManagerWrapper.getInstance().getTaskThumbnail(
                         key.id, lowResolution);
-            }
 
-            @Override
-            public void handleResult(ThumbnailData result) {
-                mCache.put(key, result);
-                callback.accept(result);
+                MAIN_EXECUTOR.execute(() -> {
+                    if (isCanceled()) {
+                        // We don't call back to the provided callback in this case
+                        return;
+                    }
+
+                    mCache.put(key, thumbnail);
+                    callback.accept(thumbnail);
+                    onEnd();
+                });
             }
         };
-        mBgExecutor.execute(request);
+        Utilities.postAsyncCallback(mBackgroundHandler, request);
         return request;
     }
 
@@ -207,6 +218,15 @@
         return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
     }
 
+    public static abstract class ThumbnailLoadRequest extends HandlerRunnable {
+        public final boolean mLowResolution;
+
+        ThumbnailLoadRequest(Handler handler, boolean lowResolution) {
+            super(handler, null);
+            mLowResolution = lowResolution;
+        }
+    }
+
     /**
      * @return Whether device supports low-res thumbnails. Low-res files are an optimization
      * for faster load times of snapshots. Devices can optionally disable low-res files so that
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 3e05b07..df93e0b 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -20,7 +20,6 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.GestureState.DEFAULT_STATE;
@@ -688,7 +687,7 @@
         final boolean disableHorizontalSwipe = mDeviceState.isInExclusionRegion(event);
         return new OtherActivityInputConsumer(this, mDeviceState, mTaskAnimationManager,
                 gestureState, shouldDefer, this::onConsumerInactive,
-                mInputMonitorCompat, mInputEventReceiver, disableHorizontalSwipe, factory);
+                mInputMonitorCompat, disableHorizontalSwipe, factory);
     }
 
     private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
@@ -710,10 +709,8 @@
 
         if (activity.getRootView().hasWindowFocus()
                 || previousGestureState.isRunningAnimationToLauncher()
-                || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
-                    && forceOverviewInputConsumer)
-                || (ENABLE_QUICKSTEP_LIVE_TILE.get())
-                    && gestureState.getActivityInterface().isInLiveTileMode()) {
+                || (FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS.get()
+                    && forceOverviewInputConsumer)) {
             return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat,
                     false /* startingInActivityBounds */);
         } else {
@@ -737,8 +734,6 @@
     private void reset() {
         mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
         mGestureState = DEFAULT_STATE;
-        // By default, use batching of the input events
-        mInputEventReceiver.setBatchingEnabled(true);
     }
 
     private void preloadOverview(boolean fromInit) {
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 8f2356c..f5f5259 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.fallback;
 
-import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.fallback.RecentsState.DEFAULT;
 import static com.android.quickstep.fallback.RecentsState.MODAL_TASK;
 
@@ -28,7 +27,6 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.quickstep.FallbackActivityInterface;
-import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
@@ -76,14 +74,14 @@
     }
 
     /**
-     * When the gesture ends and we're going to recents view, we also remove the temporary
+     * When the gesture ends and recents view become interactive, we also remove the temporary
      * invisible tile added for the home task. This also pushes the remaining tiles back
      * to the center.
      */
     @Override
-    public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-        super.onGestureEndTargetCalculated(endTarget);
-        if (mHomeTaskInfo != null && endTarget == RECENTS) {
+    public void onGestureAnimationEnd() {
+        super.onGestureAnimationEnd();
+        if (mHomeTaskInfo != null) {
             TaskView tv = getTaskView(mHomeTaskInfo.taskId);
             if (tv != null) {
                 PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 580e4ec..89e6931 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -25,6 +25,12 @@
 import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPLEFT;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction.UPRIGHT;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.FLING;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.SWIPE_NOOP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.NAVBAR;
 
 import android.animation.ValueAnimator;
 import android.content.Context;
@@ -41,6 +47,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.quickstep.BaseActivityInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -73,6 +80,7 @@
     private float mTimeFraction;
     private long mDragTime;
     private float mLastProgress;
+    private int mDirection;
     private BaseActivityInterface mActivityInterface;
 
     private final float mDragDistThreshold;
@@ -189,6 +197,8 @@
                 if (mState != STATE_DELEGATE_ACTIVE && !mLaunchedAssistant) {
                     ValueAnimator animator = ValueAnimator.ofFloat(mLastProgress, 0)
                         .setDuration(RETRACT_ANIMATION_DURATION_MS);
+                    UserEventDispatcher.newInstance(mContext).logActionOnContainer(
+                        SWIPE_NOOP, mDirection, NAVBAR);
                     animator.addUpdateListener(valueAnimator -> {
                         float progress = (float) valueAnimator.getAnimatedValue();
                         SystemUiProxy.INSTANCE.get(mContext).onAssistantProgress(progress);
@@ -213,7 +223,7 @@
             mLastProgress = Math.min(mDistance * 1f / mDragDistThreshold, 1) * mTimeFraction;
             if (mDistance >= mDragDistThreshold && mTimeFraction >= 1) {
                 SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(0);
-                startAssistantInternal();
+                startAssistantInternal(SWIPE);
 
                 Bundle args = new Bundle();
                 args.putInt(OPA_BUNDLE_TRIGGER, OPA_BUNDLE_TRIGGER_DIAG_SWIPE_GESTURE);
@@ -226,7 +236,10 @@
         }
     }
 
-    private void startAssistantInternal() {
+    private void startAssistantInternal(int gestureType) {
+        UserEventDispatcher.newInstance(mContext)
+            .logActionOnContainer(gestureType, mDirection, NAVBAR);
+
         BaseDraggingActivity launcherActivity = mActivityInterface.getCreatedActivity();
         if (launcherActivity != null) {
             launcherActivity.getRootView().performHapticFeedback(
@@ -240,6 +253,7 @@
      */
     private boolean isValidAssistantGestureAngle(float deltaX, float deltaY) {
         float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        mDirection = angle > 90 ? UPLEFT : UPRIGHT;
 
         // normalize so that angle is measured clockwise from horizontal in the bottom right corner
         // and counterclockwise from horizontal in the bottom left corner
@@ -258,7 +272,7 @@
                 mLastProgress = 1;
                 SystemUiProxy.INSTANCE.get(mContext).onAssistantGestureCompletion(
                     (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
-                startAssistantInternal();
+                startAssistantInternal(FLING);
 
                 Bundle args = new Bundle();
                 args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_GESTURE);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 35dbee9..b1a1133 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -67,7 +67,6 @@
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 import java.util.function.Consumer;
@@ -93,7 +92,6 @@
     private RecentsAnimationCallbacks mActiveCallbacks;
     private final CachedEventDispatcher mRecentsViewDispatcher = new CachedEventDispatcher();
     private final InputMonitorCompat mInputMonitorCompat;
-    private final InputEventReceiver mInputEventReceiver;
     private final BaseActivityInterface mActivityInterface;
 
     private final AbsSwipeUpHandler.Factory mHandlerFactory;
@@ -137,8 +135,8 @@
     public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
-            InputMonitorCompat inputMonitorCompat, InputEventReceiver inputEventReceiver,
-            boolean disableHorizontalSwipe, Factory handlerFactory) {
+            InputMonitorCompat inputMonitorCompat, boolean disableHorizontalSwipe,
+            Factory handlerFactory) {
         super(base);
         mDeviceState = deviceState;
         mNavBarPosition = mDeviceState.getNavBarPosition();
@@ -156,7 +154,6 @@
         mOnCompleteCallback = onCompleteCallback;
         mVelocityTracker = VelocityTracker.obtain();
         mInputMonitorCompat = inputMonitorCompat;
-        mInputEventReceiver = inputEventReceiver;
 
         boolean continuingPreviousGesture = mTaskAnimationManager.isRecentsAnimationRunning();
         mIsDeferredDownTarget = !continuingPreviousGesture && isDeferredDownTarget;
@@ -218,9 +215,6 @@
 
         switch (ev.getActionMasked()) {
             case ACTION_DOWN: {
-                // Until we detect the gesture, handle events as we receive them
-                mInputEventReceiver.setBatchingEnabled(false);
-
                 Object traceToken = TraceHelper.INSTANCE.beginSection(DOWN_EVT,
                         FLAG_CHECK_FOR_RACE_CONDITIONS);
                 mActivePointerId = ev.getPointerId(0);
@@ -357,8 +351,6 @@
         }
         TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
         mInputMonitorCompat.pilferPointers();
-        // Once we detect the gesture, we can enable batching to reduce further updates
-        mInputEventReceiver.setBatchingEnabled(true);
 
         mActivityInterface.closeOverlay();
         ActivityManagerWrapper.getInstance().closeSystemWindows(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 924b32c..1c77a05 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,7 +15,6 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 
@@ -28,6 +27,9 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
@@ -80,12 +82,17 @@
         mContext.startActivity(mGestureState.getHomeIntent());
         ActiveGestureLog.INSTANCE.addLog("startQuickstep");
         BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
-        int state = (mGestureState != null && mGestureState.getEndTarget() != null)
+        int pageIndex = -1; // This number doesn't reflect workspace page index.
+                            // It only indicates that launcher client screen was shown.
+        int containerType = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
-                : LAUNCHER_STATE_HOME;
+                : LauncherLogProto.ContainerType.WORKSPACE;
+        activity.getUserEventDispatcher().logActionOnContainer(
+                wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
+        activity.getUserEventDispatcher().setPreviousHomeGesture(true);
         activity.getStatsLogManager().logger()
-                .withSrcState(LAUNCHER_STATE_BACKGROUND)
-                .withDstState(state)
+                .withSrcState(LAUNCHER_STATE_HOME)
+                .withDstState(LAUNCHER_STATE_HOME)
                 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                         .setWorkspace(
                                 LauncherAtom.WorkspaceContainer.newBuilder()
diff --git a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
index 16886ec..70181fb 100644
--- a/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/AssistantGestureTutorialFragment.java
@@ -24,7 +24,7 @@
 /** Shows the Home gesture interactive tutorial. */
 public class AssistantGestureTutorialFragment extends TutorialFragment {
     @Override
-    Integer getHandAnimationResId() {
+    int getHandAnimationResId() {
         return R.drawable.assistant_gesture;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
index 41db684..bef50ea 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java
@@ -24,7 +24,7 @@
 /** Shows the Back gesture interactive tutorial. */
 public class BackGestureTutorialFragment extends TutorialFragment {
     @Override
-    Integer getHandAnimationResId() {
+    int getHandAnimationResId() {
         return R.drawable.back_gesture;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 9489bac..e4b348e 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -143,10 +143,6 @@
         return false;
     }
 
-    boolean onInterceptTouch(MotionEvent motionEvent) {
-        return isWithinTouchRegion((int) motionEvent.getX(), (int) motionEvent.getY());
-    }
-
     private boolean isWithinTouchRegion(int x, int y) {
         // Disallow if too far from the edge
         if (x > mEdgeWidth + mLeftInset && x < (mDisplaySize.x - mEdgeWidth - mRightInset)) {
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
index 63595a5..e2a9d12 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialFragment.java
@@ -21,7 +21,7 @@
 /** Shows the Home gesture interactive tutorial. */
 public class HomeGestureTutorialFragment extends TutorialFragment {
     @Override
-    Integer getHandAnimationResId() {
+    int getHandAnimationResId() {
         return R.drawable.home_gesture;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index d1b0a70..f897ecc 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -250,12 +250,6 @@
         return intercepted;
     }
 
-    boolean onInterceptTouch(MotionEvent event) {
-        return mAssistantLeftRegion.contains(event.getX(), event.getY())
-                || mAssistantRightRegion.contains(event.getX(), event.getY())
-                || event.getY() >= mDisplaySize.y - mBottomGestureHeight;
-    }
-
     protected void onMotionPauseDetected() {
         VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
index 93200bb..3357b70 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialFragment.java
@@ -21,7 +21,7 @@
 /** Shows the Overview gesture interactive tutorial. */
 public class OverviewGestureTutorialFragment extends TutorialFragment {
     @Override
-    Integer getHandAnimationResId() {
+    int getHandAnimationResId() {
         return R.drawable.overview_gesture;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java b/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
deleted file mode 100644
index db1afc2..0000000
--- a/quickstep/src/com/android/quickstep/interaction/RootSandboxLayout.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.interaction;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.RelativeLayout;
-
-import androidx.fragment.app.FragmentManager;
-
-/** Root layout that TutorialFragment uses to intercept motion events. */
-public class RootSandboxLayout extends RelativeLayout {
-    public RootSandboxLayout(Context context) {
-        super(context);
-    }
-
-    public RootSandboxLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public RootSandboxLayout(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
-        return ((TutorialFragment) FragmentManager.findFragment(this))
-                .onInterceptTouch(motionEvent);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxLauncherRenderer.java b/quickstep/src/com/android/quickstep/interaction/SandboxLauncherRenderer.java
deleted file mode 100644
index 80ffe66..0000000
--- a/quickstep/src/com/android/quickstep/interaction/SandboxLauncherRenderer.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.interaction;
-
-import android.content.Context;
-import android.view.View;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.graphics.LauncherPreviewRenderer;
-
-/** Renders a fake Launcher for use in the Sandbox. */
-class SandboxLauncherRenderer extends LauncherPreviewRenderer {
-    SandboxLauncherRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) {
-        super(context, idp, migrated);
-    }
-
-    @Override
-    public boolean shouldShowRealLauncherPreview() {
-        return false;
-    }
-
-    @Override
-    public boolean shouldShowQsb() {
-        return false;
-    }
-
-    @Override
-    public View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
-        return null;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialController.java
deleted file mode 100644
index d54efc5..0000000
--- a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialController.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.interaction;
-
-import android.graphics.PointF;
-import android.view.View;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureResult;
-import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureResult;
-
-/** A {@link TutorialController} for the Sandbox Mode. */
-public class SandboxModeTutorialController extends SwipeUpGestureTutorialController {
-
-    SandboxModeTutorialController(SandboxModeTutorialFragment fragment, TutorialType tutorialType) {
-        super(fragment, tutorialType);
-    }
-
-    @Nullable
-    @Override
-    Integer getTitleStringId() {
-        return R.string.sandbox_mode_title;
-    }
-
-    @Nullable
-    @Override
-    Integer getSubtitleStringId() {
-        return R.string.sandbox_mode_subtitle;
-    }
-
-    @Nullable
-    @Override
-    Integer getActionButtonStringId() {
-        return R.string.gesture_tutorial_action_button_label_done;
-    }
-
-    @Override
-    void onActionButtonClicked(View button) {
-        mTutorialFragment.closeTutorial();
-    }
-
-    @Override
-    public void onBackGestureAttempted(BackGestureResult result) {
-        switch (result) {
-            case BACK_COMPLETED_FROM_LEFT:
-            case BACK_COMPLETED_FROM_RIGHT:
-                showRippleEffect(null);
-                showFeedback(R.string.sandbox_mode_back_gesture_feedback_successful);
-                break;
-            case BACK_CANCELLED_FROM_RIGHT:
-                showFeedback(R.string.back_gesture_feedback_cancelled_right_edge);
-                break;
-            case BACK_CANCELLED_FROM_LEFT:
-                showFeedback(R.string.back_gesture_feedback_cancelled_left_edge);
-                break;
-            case BACK_NOT_STARTED_TOO_FAR_FROM_EDGE:
-                showFeedback(R.string.sandbox_mode_back_gesture_feedback_swipe_too_far_from_edge);
-                break;
-        }
-    }
-
-    @Override
-    public void onNavBarGestureAttempted(NavBarGestureResult result, PointF finalVelocity) {
-        switch (result) {
-            case ASSISTANT_COMPLETED:
-                showRippleEffect(null);
-                showFeedback(R.string.sandbox_mode_assistant_gesture_feedback_successful);
-                break;
-            case HOME_GESTURE_COMPLETED:
-                animateFakeTaskViewHome(finalVelocity, () -> {
-                    showFeedback(R.string.sandbox_mode_home_gesture_feedback_successful);
-                });
-                break;
-            case OVERVIEW_GESTURE_COMPLETED:
-                fadeOutFakeTaskView(true, () -> {
-                    showFeedback(R.string.sandbox_mode_overview_gesture_feedback_successful);
-                });
-                break;
-            case HOME_OR_OVERVIEW_NOT_STARTED_WRONG_SWIPE_DIRECTION:
-            case HOME_OR_OVERVIEW_CANCELLED:
-            case HOME_NOT_STARTED_TOO_FAR_FROM_EDGE:
-            case OVERVIEW_NOT_STARTED_TOO_FAR_FROM_EDGE:
-                showFeedback(R.string.home_gesture_feedback_swipe_too_far_from_edge);
-                break;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
deleted file mode 100644
index 955a2f7..0000000
--- a/quickstep/src/com/android/quickstep/interaction/SandboxModeTutorialFragment.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.interaction;
-
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.quickstep.interaction.TutorialController.TutorialType;
-
-/** Shows the general navigation gesture sandbox environment. */
-public class SandboxModeTutorialFragment extends TutorialFragment {
-
-    @Override
-    TutorialController createController(TutorialType type) {
-        return new SandboxModeTutorialController(this, type);
-    }
-
-    @Override
-    Class<? extends TutorialController> getControllerClass() {
-        return SandboxModeTutorialController.class;
-    }
-
-    @Override
-    public boolean onTouch(View view, MotionEvent motionEvent) {
-        if (motionEvent.getAction() == MotionEvent.ACTION_DOWN && mTutorialController != null) {
-            mTutorialController.setRippleHotspot(motionEvent.getX(), motionEvent.getY());
-        }
-        return super.onTouch(view, motionEvent);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 2198ade..73f1f8c 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -47,12 +47,11 @@
     final TextView mTitleTextView;
     final TextView mSubtitleTextView;
     final TextView mFeedbackView;
-    final View mLauncherView;
     final ClipIconView mFakeIconView;
     final View mFakeTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
-    @Nullable final TutorialHandAnimation mHandCoachingAnimation;
+    final TutorialHandAnimation mHandCoachingAnimation;
     final ImageView mHandCoachingView;
     final Button mActionTextButton;
     final Button mActionButton;
@@ -69,7 +68,6 @@
         mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view);
         mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view);
         mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view);
-        mLauncherView = tutorialFragment.getLauncherView();
         mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view);
         mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view);
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
@@ -145,16 +143,13 @@
     void onActionTextButtonClicked(View button) {}
 
     void showHandCoachingAnimation() {
-        if (isComplete() || mHandCoachingAnimation == null) {
+        if (isComplete()) {
             return;
         }
         mHandCoachingAnimation.startLoopedAnimation(mTutorialType);
     }
 
     void hideHandCoachingAnimation() {
-        if (mHandCoachingAnimation == null) {
-            return;
-        }
         mHandCoachingAnimation.stop();
         mHandCoachingView.setVisibility(View.INVISIBLE);
     }
@@ -167,10 +162,8 @@
 
         if (isComplete()) {
             hideHandCoachingAnimation();
-            mLauncherView.setVisibility(View.INVISIBLE);
         } else {
             showHandCoachingAnimation();
-            mLauncherView.setVisibility(View.VISIBLE);
         }
     }
 
@@ -213,8 +206,7 @@
         return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE
                 || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE
                 || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE
-                || mTutorialType == TutorialType.ASSISTANT_COMPLETE
-                || mTutorialType == TutorialType.SANDBOX_MODE;
+                || mTutorialType == TutorialType.ASSISTANT_COMPLETE;
     }
 
     /** Denotes the type of the tutorial. */
@@ -227,7 +219,6 @@
         OVERVIEW_NAVIGATION,
         OVERVIEW_NAVIGATION_COMPLETE,
         ASSISTANT,
-        ASSISTANT_COMPLETE,
-        SANDBOX_MODE
+        ASSISTANT_COMPLETE
     }
 }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index f297d5a..9a8264d 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -31,7 +31,6 @@
 import androidx.fragment.app.Fragment;
 import androidx.fragment.app.FragmentActivity;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.R;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
 
@@ -43,10 +42,9 @@
     TutorialType mTutorialType;
     @Nullable TutorialController mTutorialController = null;
     View mRootView;
-    @Nullable TutorialHandAnimation mHandCoachingAnimation = null;
+    TutorialHandAnimation mHandCoachingAnimation;
     EdgeBackGestureHandler mEdgeBackGestureHandler;
     NavBarGestureHandler mNavBarGestureHandler;
-    private View mLauncherView;
 
     public static TutorialFragment newInstance(TutorialType tutorialType) {
         TutorialFragment fragment = getFragmentForTutorialType(tutorialType);
@@ -76,17 +74,13 @@
             case ASSISTANT:
             case ASSISTANT_COMPLETE:
                 return new AssistantGestureTutorialFragment();
-            case SANDBOX_MODE:
-                return new SandboxModeTutorialFragment();
             default:
                 Log.e(LOG_TAG, "Failed to find an appropriate fragment for " + tutorialType.name());
         }
         return null;
     }
 
-    @Nullable Integer getHandAnimationResId() {
-        return null;
-    }
+    abstract int getHandAnimationResId();
 
     abstract TutorialController createController(TutorialType type);
 
@@ -120,14 +114,8 @@
             return insets;
         });
         mRootView.setOnTouchListener(this);
-        Integer handAnimationResId = getHandAnimationResId();
-        if (handAnimationResId != null) {
-            mHandCoachingAnimation =
-                new TutorialHandAnimation(getContext(), mRootView, handAnimationResId);
-        }
-        InvariantDeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(getContext());
-        mLauncherView = new SandboxLauncherRenderer(getContext(), dp, true).getRenderedView();
-        ((ViewGroup) mRootView).addView(mLauncherView, 0);
+        mHandCoachingAnimation = new TutorialHandAnimation(getContext(), mRootView,
+                getHandAnimationResId());
         return mRootView;
     }
 
@@ -140,25 +128,16 @@
     @Override
     public void onPause() {
         super.onPause();
-
-        if (mHandCoachingAnimation != null) {
-            mHandCoachingAnimation.stop();
-        }
+        mHandCoachingAnimation.stop();
     }
 
     @Override
     public boolean onTouch(View view, MotionEvent motionEvent) {
-        // Note: Using logical-or to ensure both functions get called.
+        // Note: Using logical or to ensure both functions get called.
         return mEdgeBackGestureHandler.onTouch(view, motionEvent)
                 | mNavBarGestureHandler.onTouch(view, motionEvent);
     }
 
-    boolean onInterceptTouch(MotionEvent motionEvent) {
-        // Note: Using logical-or to ensure both functions get called.
-        return mEdgeBackGestureHandler.onInterceptTouch(motionEvent)
-                | mNavBarGestureHandler.onInterceptTouch(motionEvent);
-    }
-
     void onAttachedToWindow() {
         mEdgeBackGestureHandler.setViewGroupParent((ViewGroup) getRootView());
     }
@@ -189,11 +168,7 @@
         return mRootView;
     }
 
-    View getLauncherView() {
-        return mLauncherView;
-    }
-
-    @Nullable TutorialHandAnimation getHandAnimation() {
+    TutorialHandAnimation getHandAnimation() {
         return mHandCoachingAnimation;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/CancellableTask.java b/quickstep/src/com/android/quickstep/util/CancellableTask.java
deleted file mode 100644
index a6e2e81..0000000
--- a/quickstep/src/com/android/quickstep/util/CancellableTask.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.WorkerThread;
-
-/**
- * Utility class to executore a task on background and post the result on UI thread
- */
-public abstract class CancellableTask<T> implements Runnable {
-
-    private boolean mCancelled = false;
-
-    @Override
-    public final void run() {
-        if (mCancelled) {
-            return;
-        }
-        T result = getResultOnBg();
-        if (mCancelled) {
-            return;
-        }
-        MAIN_EXECUTOR.execute(() -> {
-            if (mCancelled) {
-                return;
-            }
-            handleResult(result);
-        });
-    }
-
-    /**
-     * Called on the worker thread to process the request. The return object is passed to
-     * {@link #handleResult(Object)}
-     */
-    @WorkerThread
-    public abstract T getResultOnBg();
-
-    /**
-     * Called on the UI thread to handle the final result.
-     * @param result
-     */
-    @UiThread
-    public abstract void handleResult(T result);
-
-    /**
-     * Cancels the request. If it is called before {@link #handleResult(Object)}, that method
-     * will not be called
-     */
-    public void cancel() {
-        mCancelled = true;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 1031c5b..4a298d3 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -18,10 +18,8 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
-import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
-import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
@@ -112,7 +110,7 @@
         boolean isLayoutNaturalToLauncher = recentsView.getPagedOrientationHandler()
                 .isLayoutNaturalToLauncher();
         config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, isLayoutNaturalToLauncher
-                ? clampToProgress(FAST_OUT_SLOW_IN, 0, 0.75f) : FINAL_FRAME);
+                ? DEACCEL : FINAL_FRAME);
         config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, FINAL_FRAME);
         config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
         config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, INSTANT);
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index de44e07..df9b0cf 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -23,6 +23,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.launcher3.logging.LoggerUtils.extractObjectNameAndAddress;
 import static com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
@@ -71,7 +72,6 @@
 
     private static final String TAG = "RecentsOrientedState";
     private static final boolean DEBUG = false;
-    private static final String DELIMITER_DOT = "\\.";
 
     private ContentObserver mSystemAutoRotateObserver = new ContentObserver(new Handler()) {
         @Override
@@ -534,13 +534,4 @@
                 ? idp.landscapeProfile
                 : idp.portraitProfile;
     }
-
-    /**
-     * String conversion for only the helpful parts of {@link Object#toString()} method
-     * @param stringToExtract "foo.bar.baz.MyObject@1234"
-     * @return "MyObject@1234"
-     */
-    private static String extractObjectNameAndAddress(String stringToExtract) {
-        return stringToExtract.substring(stringToExtract.lastIndexOf(DELIMITER_DOT));
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 19c6588..04308c8 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -21,8 +21,6 @@
 import android.os.Handler;
 
 import com.android.launcher3.LauncherAnimationRunner;
-import com.android.launcher3.LauncherAnimationRunner.AnimationResult;
-import com.android.launcher3.WrappedAnimationRunnerImpl;
 import com.android.launcher3.WrappedLauncherAnimationRunner;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -30,17 +28,14 @@
 
 public abstract class RemoteAnimationProvider {
 
-    WrappedAnimationRunnerImpl mAnimationRunner;
+    LauncherAnimationRunner mAnimationRunner;
 
     public abstract AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets);
 
     ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
-        mAnimationRunner = new WrappedAnimationRunnerImpl() {
-            @Override
-            public Handler getHandler() {
-                return handler;
-            }
+        mAnimationRunner = new LauncherAnimationRunner(handler,
+                false /* startAtFrontOfQueue */) {
 
             @Override
             public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
@@ -50,6 +45,7 @@
         };
         final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner(
                 mAnimationRunner, false /* startAtFrontOfQueue */);
+
         return ActivityOptionsCompat.makeRemoteAnimation(
                 new RemoteAnimationAdapterCompat(wrapper, duration, 0));
     }
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 3a54bd6..903e87a 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -278,6 +278,7 @@
             int start = mOrientationState.getOrientationHandler()
                     .getPrimaryValue(mTaskRect.left, mTaskRect.top);
             mScrollState.screenCenter = start + mScrollState.scroll + mScrollState.halfPageSize;
+            mScrollState.pageParentScale = recentsViewScale.value;
             mScrollState.updateInterpolation(start);
             mCurveScale = TaskView.getCurveScaleForInterpolation(mScrollState.linearInterpolation);
         }
@@ -296,9 +297,9 @@
         mMatrix.postTranslate(insets.left, insets.top);
         mMatrix.postScale(scale, scale);
 
-        // Apply TaskView matrix: scale, translate, scroll
-        mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
+        // Apply TaskView matrix: translate, scale, scroll
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top + mOffsetY);
+        mMatrix.postScale(mCurveScale, mCurveScale, taskWidth / 2, taskHeight / 2);
         mOrientationState.getOrientationHandler().set(
                 mMatrix, MATRIX_POST_TRANSLATE, mScrollState.scroll);
 
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 95bde80..b06d4bc 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.time.Duration;
@@ -216,8 +217,8 @@
                     view, 0, 0,
                     view.getWidth(), view.getHeight());
             activity.startActivity(intent, options.toBundle());
-
-            // TODO: add WW logging on the app usage settings click.
+            activity.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+                    LauncherLogProto.ControlType.APP_USAGE_SETTINGS, view);
         } catch (ActivityNotFoundException e) {
             Log.e(TAG, "Failed to open app usage settings for task "
                     + mTask.getTopComponent().getPackageName(), e);
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 52a7466..b338bd0 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -36,6 +36,7 @@
 import android.widget.FrameLayout;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.StateManager.StateListener;
@@ -176,8 +177,14 @@
 
     @Override
     protected boolean shouldStealTouchFromSiblingsBelow(MotionEvent ev) {
-        return mActivity.getStateManager().getState().overviewUi
-                && super.shouldStealTouchFromSiblingsBelow(ev);
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            // Allow touches to go through to the hotseat.
+            Hotseat hotseat = mActivity.getHotseat();
+            boolean touchingHotseat = hotseat.isShown()
+                    && mActivity.getDragLayer().isEventOverView(hotseat, ev, this);
+            return !touchingHotseat;
+        }
+        return super.shouldStealTouchFromSiblingsBelow(ev);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index b8e07cb..2a6c9e9 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -54,6 +54,7 @@
             HIDDEN_UNSUPPORTED_NAVIGATION,
             HIDDEN_NON_ZERO_ROTATION,
             HIDDEN_NO_TASKS,
+            HIDDEN_GESTURE_RUNNING,
             HIDDEN_NO_RECENTS})
     @Retention(RetentionPolicy.SOURCE)
     public @interface ActionsHiddenFlags { }
@@ -61,7 +62,8 @@
     public static final int HIDDEN_UNSUPPORTED_NAVIGATION = 1 << 0;
     public static final int HIDDEN_NON_ZERO_ROTATION = 1 << 1;
     public static final int HIDDEN_NO_TASKS = 1 << 2;
-    public static final int HIDDEN_NO_RECENTS = 1 << 3;
+    public static final int HIDDEN_GESTURE_RUNNING = 1 << 3;
+    public static final int HIDDEN_NO_RECENTS = 1 << 4;
 
     @IntDef(flag = true, value = {
             DISABLED_SCROLLING,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 7a8e11d..0362377 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -35,14 +35,16 @@
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_CLEAR_ALL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
+import static com.android.quickstep.views.OverviewActionsView.HIDDEN_GESTURE_RUNNING;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
@@ -107,6 +109,7 @@
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
@@ -115,7 +118,6 @@
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.ViewPool;
 import com.android.quickstep.BaseActivityInterface;
-import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
@@ -1127,6 +1129,7 @@
         setEnableDrawingLiveTile(false);
         setRunningTaskHidden(true);
         setRunningTaskIconScaledDown(true);
+        mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, true);
     }
 
     /**
@@ -1180,14 +1183,7 @@
     }
 
     /**
-     * Called when a gesture from an app has finished, and an end target has been determined.
-     */
-    public void onGestureEndTargetCalculated(GestureState.GestureEndTarget endTarget) {
-
-    }
-
-    /**
-     * Called when a gesture from an app has finished, and the animation to the target has ended.
+     * Called when a gesture from an app has finished.
      */
     public void onGestureAnimationEnd() {
         if (mOrientationState.setGestureActive(false)) {
@@ -1326,6 +1322,7 @@
     }
 
     private void animateActionsViewIn() {
+        mActionsView.updateHiddenFlags(HIDDEN_GESTURE_RUNNING, false);
         ObjectAnimator anim = ObjectAnimator.ofFloat(
                 mActionsView.getVisibilityAlpha(), MultiValueAlpha.VALUE, 0, 1);
         anim.setDuration(TaskView.SCALE_ICON_DURATION);
@@ -1410,12 +1407,13 @@
          * Updates linearInterpolation for the provided child position
          */
         public void updateInterpolation(float childStart) {
-            float pageCenter = childStart + halfPageSize;
+            float scaledHalfPageSize = halfPageSize / pageParentScale;
+            float pageCenter = childStart + scaledHalfPageSize;
             float distanceFromScreenCenter = screenCenter - pageCenter;
             // How far the page has to move from the center to be offscreen, taking into account
             // the EDGE_SCALE_DOWN_FACTOR that will be applied at that position.
             float distanceToReachEdge = halfScreenSize
-                    + halfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR);
+                    + scaledHalfPageSize * (1 - TaskView.EDGE_SCALE_DOWN_FACTOR);
             linearInterpolation = Math.min(1,
                     Math.abs(distanceFromScreenCenter) / distanceToReachEdge);
         }
@@ -1461,7 +1459,7 @@
     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
             boolean shouldRemoveTask, long duration) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.finish(false, Touch.SWIPE);
         }
         PendingAnimation anim = new PendingAnimation(duration);
 
@@ -1623,7 +1621,7 @@
     protected void runDismissAnimation(PendingAnimation pendingAnim) {
         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
         controller.dispatchOnStart();
-        controller.setEndAction(() -> pendingAnim.finish(true));
+        controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
         controller.start();
     }
@@ -1636,7 +1634,7 @@
     @SuppressWarnings("unused")
     private void dismissAllTasks(View view) {
         runDismissAnimation(createAllTasksDismissAnimation(DISMISS_TASK_DURATION));
-        mActivity.getStatsLogManager().logger().log(LAUNCHER_TASK_CLEAR_ALL);
+        mActivity.getUserEventDispatcher().logActionOnControl(TAP, CLEAR_ALL_BUTTON);
     }
 
     private void dismissCurrentTask() {
@@ -2161,12 +2159,7 @@
                         tv.notifyTaskLaunchFailed(TAG);
                     }
                 };
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                    finishRecentsAnimation(false /* toRecents */, null);
-                    onLaunchResult.accept(true /* success */);
-                } else {
-                    tv.launchTask(false, onLaunchResult, getHandler());
-                }
+                tv.launchTask(false, onLaunchResult, getHandler());
                 Task task = tv.getTask();
                 if (task != null) {
                     mActivity.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 4aff7e3..d47eba6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.views;
 
-import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.quickstep.views.TaskThumbnailView.DIM_ALPHA;
 
 import android.animation.Animator;
@@ -230,16 +229,7 @@
                 menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
         LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
         mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp);
-        menuOptionView.setOnClickListener(view -> {
-            if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                RecentsView recentsView = mTaskView.getRecentsView();
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                () -> menuOption.onClick(view)));
-            } else {
-                menuOption.onClick(view);
-            }
-        });
+        menuOptionView.setOnClickListener(menuOption);
         mOptionLayout.addView(menuOptionView);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 3d6d423..321821b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -73,11 +73,15 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.launcher3.util.ViewPool.Reusable;
@@ -86,7 +90,6 @@
 import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskUtils;
-import com.android.quickstep.util.CancellableTask;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.TaskCornerRadius;
 import com.android.quickstep.views.RecentsView.PageCallbacks;
@@ -186,8 +189,8 @@
     private boolean mShowScreenshot;
 
     // The current background requests to load the task thumbnail and icon
-    private CancellableTask mThumbnailLoadRequest;
-    private CancellableTask mIconLoadRequest;
+    private TaskThumbnailCache.ThumbnailLoadRequest mThumbnailLoadRequest;
+    private TaskIconCache.IconLoadRequest mIconLoadRequest;
 
     // Order in which the footers appear. Lower order appear below higher order.
     public static final int INDEX_DIGITAL_WELLBEING_TOAST = 0;
@@ -217,7 +220,6 @@
             }
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
                 if (isRunningTask()) {
-                    // TODO: Replace this animation with createRecentsWindowAnimator
                     createLaunchAnimationForRunningTask().start();
                 } else {
                     launchTask(true /* animate */);
@@ -363,7 +365,10 @@
         final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
                 this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
         AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController();
-        currentAnimation.setEndAction(() -> pendingAnimation.finish(true));
+        currentAnimation.setEndAction(() -> {
+            pendingAnimation.finish(true, Touch.SWIPE);
+            launchTask(false);
+        });
         return currentAnimation;
     }
 
@@ -386,6 +391,20 @@
 
     public void launchTask(boolean animate, boolean freezeTaskList, Consumer<Boolean> resultCallback,
             Handler resultCallbackHandler) {
+        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
+            if (isRunningTask()) {
+                getRecentsView().finishRecentsAnimation(false /* toRecents */,
+                        () -> resultCallbackHandler.post(() -> resultCallback.accept(true)));
+            } else {
+                launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
+            }
+        } else {
+            launchTaskInternal(animate, freezeTaskList, resultCallback, resultCallbackHandler);
+        }
+    }
+
+    private void launchTaskInternal(boolean animate, boolean freezeTaskList,
+            Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
         if (mTask != null) {
             final ActivityOptions opts;
             TestLogging.recordEvent(
@@ -462,13 +481,15 @@
         }
     }
 
-    private boolean showTaskMenu() {
+    private boolean showTaskMenu(int action) {
         if (!getRecentsView().isClearAllHidden()) {
             getRecentsView().snapToPage(getRecentsView().indexOfChild(this));
         } else {
             mMenuView = TaskMenuView.showForTask(this);
             mActivity.getStatsLogManager().logger().withItemInfo(getItemInfo())
                     .log(LAUNCHER_TASK_ICON_TAP_OR_LONGPRESS);
+            UserEventDispatcher.newInstance(getContext()).logActionOnItem(action, Direction.NONE,
+                    LauncherLogProto.ItemType.TASK_ICON);
             if (mMenuView != null) {
                 mMenuView.addOnAttachStateChangeListener(mTaskMenuStateListener);
             }
@@ -479,10 +500,10 @@
     private void setIcon(Drawable icon) {
         if (icon != null) {
             mIconView.setDrawable(icon);
-            mIconView.setOnClickListener(v -> showTaskMenu());
+            mIconView.setOnClickListener(v -> showTaskMenu(Touch.TAP));
             mIconView.setOnLongClickListener(v -> {
                 requestDisallowInterceptTouchEvent(true);
-                return showTaskMenu();
+                return showTaskMenu(Touch.LONGPRESS);
             });
         } else {
             mIconView.setDrawable(null);
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 2a7da7e..ccfa3fc 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -3,8 +3,6 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -13,12 +11,12 @@
 import android.app.PendingIntent;
 import android.app.usage.UsageStatsManager;
 import android.content.Intent;
+import android.os.Build;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.views.DigitalWellBeingToast;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -36,6 +34,9 @@
 
     @Test
     public void testToast() throws Exception {
+        // b/150303529
+        if (Build.MODEL.contains("Cuttlefish")) return;
+
         startAppFast(CALCULATOR_PACKAGE);
 
         final UsageStatsManager usageStatsManager =
diff --git a/res/layout/search_result_icon_row.xml b/res/layout/search_result_icon_row.xml
deleted file mode 100644
index 5ecc0c2..0000000
--- a/res/layout/search_result_icon_row.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.views.SearchResultIconRow xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
-    style="@style/BaseIcon"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="horizontal"
-    android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
-    android:gravity="start|center_vertical"
-    android:textAlignment="viewStart"
-    android:textColor="?android:attr/textColorPrimary"
-    android:textSize="16sp"
-    android:padding="@dimen/dynamic_grid_edge_margin"
-    launcher:iconDisplay="hero_app"
-    launcher:layoutHorizontal="true"
-    >
-
-</com.android.launcher3.views.SearchResultIconRow>
\ No newline at end of file
diff --git a/res/layout/search_result_shortcut.xml b/res/layout/search_result_shortcut.xml
new file mode 100644
index 0000000..c350c97
--- /dev/null
+++ b/res/layout/search_result_shortcut.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.launcher3.views.SearchResultShortcut xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal"
+    android:gravity="center_vertical"
+    android:padding="@dimen/dynamic_grid_edge_margin">
+
+    <com.android.launcher3.BubbleTextView
+        android:id="@+id/bubble_text"
+        style="@style/BaseIcon"
+        android:drawablePadding="@dimen/dynamic_grid_icon_drawable_padding"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:textColor="?android:attr/textColorPrimary"
+        android:textSize="16sp"
+        android:layout_height="wrap_content"
+        launcher:iconDisplay="hero_app"
+        launcher:layoutHorizontal="true" />
+
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/deep_shortcut_icon_size"
+        android:layout_height="@dimen/deep_shortcut_icon_size"
+        android:layout_gravity="start|center_vertical"
+        android:background="@drawable/ic_deepshortcut_placeholder" />
+
+</com.android.launcher3.views.SearchResultShortcut>
\ No newline at end of file
diff --git a/res/layout/search_result_suggest.xml b/res/layout/search_result_suggest.xml
deleted file mode 100644
index c5d96f0..0000000
--- a/res/layout/search_result_suggest.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.views.SearchResultSuggestRow xmlns:android="http://schemas.android.com/apk/res/android"
-    style="@style/TextHeadline"
-    android:id="@+id/section_title"
-    android:background="?android:attr/selectableItemBackground"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical"
-    android:gravity="center_vertical"
-    android:padding="4dp"
-    android:minHeight="48dp"
-    android:textColor="?android:attr/textColorPrimary"
-    android:textSize="14sp">
-
-    <TextView
-        android:id="@+id/title"
-        style="@style/TextTitle"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:paddingStart="8dp"
-        android:layout_marginBottom="4dp"
-        android:textColor="?android:attr/textColorPrimary"
-        android:textSize="14sp" />
-
-</com.android.launcher3.views.SearchResultSuggestRow>
\ No newline at end of file
diff --git a/res/layout/search_result_thumbnail.xml b/res/layout/search_result_thumbnail.xml
deleted file mode 100644
index 0cc5a29..0000000
--- a/res/layout/search_result_thumbnail.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<com.android.launcher3.views.ThumbnailSearchResultView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="125dp"
-    android:layout_height="125dp"/>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 46b8c23..325b62f 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -59,6 +59,7 @@
     <bool name="hotseat_transpose_layout_with_orientation">true</bool>
 
     <!-- Various classes overriden by projects/build flavors. -->
+    <string name="user_event_dispatcher_class" translatable="false"></string>
     <string name="folder_name_provider_class" translatable="false"></string>
     <string name="stats_log_manager_class" translatable="false"></string>
     <string name="app_transition_manager_class" translatable="false"></string>
diff --git a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
index baae2a6..0ca5ce6 100644
--- a/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
+++ b/robolectric_tests/src/com/android/launcher3/secondarydisplay/SDWorkModeTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.os.UserManager;
@@ -29,6 +30,8 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.allapps.AllAppsPagedView;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.shadows.ShadowOverrides;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
 
@@ -66,6 +69,8 @@
         mModelHelper = new LauncherModelHelper();
         mTargetContext = RuntimeEnvironment.application;
         mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+        ShadowOverrides.setProvider(UserEventDispatcher.class,
+                c -> mock(UserEventDispatcher.class));
         Settings.Global.putFloat(mTargetContext.getContentResolver(),
                 Settings.Global.WINDOW_ANIMATION_SCALE, 0);
 
diff --git a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
index 957ae77..d330d10 100644
--- a/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
+++ b/robolectric_tests/src/com/android/launcher3/ui/LauncherUIScrollTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.mockito.Mockito.mock;
 
 import android.content.Context;
 import android.os.SystemClock;
@@ -35,6 +36,8 @@
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.folder.FolderPagedView;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.shadows.ShadowOverrides;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherLayoutBuilder.FolderBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -67,6 +70,8 @@
         mModelHelper = new LauncherModelHelper();
         mTargetContext = RuntimeEnvironment.application;
         mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext);
+        ShadowOverrides.setProvider(UserEventDispatcher.class,
+                c -> mock(UserEventDispatcher.class));
 
         Settings.Global.putFloat(mTargetContext.getContentResolver(),
                 Settings.Global.WINDOW_ANIMATION_SCALE, 0);
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 5e50e27..310c306 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -37,6 +37,8 @@
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.UserEventDispatcher;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
@@ -80,6 +82,7 @@
             new ArrayList<>();
 
     protected DeviceProfile mDeviceProfile;
+    protected UserEventDispatcher mUserEventDispatcher;
     protected StatsLogManager mStatsLogManager;
     protected SystemUiController mSystemUiController;
 
@@ -141,6 +144,8 @@
         return mDeviceProfile;
     }
 
+    public void modifyUserEvent(LauncherLogProto.LauncherEvent event) {}
+
     public final StatsLogManager getStatsLogManager() {
         if (mStatsLogManager == null) {
             mStatsLogManager = StatsLogManager.newInstance(this);
@@ -148,6 +153,13 @@
         return mStatsLogManager;
     }
 
+    public final UserEventDispatcher getUserEventDispatcher() {
+        if (mUserEventDispatcher == null) {
+            mUserEventDispatcher = UserEventDispatcher.newInstance(this);
+        }
+        return mUserEventDispatcher;
+    }
+
     public SystemUiController getSystemUiController() {
         if (mSystemUiController == null) {
             mSystemUiController = new SystemUiController(getWindow());
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 1e5a9e4..06bb263 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -23,7 +23,6 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -31,8 +30,6 @@
 import android.graphics.Color;
 import android.graphics.Paint;
 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;
@@ -46,8 +43,6 @@
 import android.view.ViewDebug;
 import android.widget.TextView;
 
-import androidx.core.graphics.ColorUtils;
-
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dot.DotInfo;
@@ -55,7 +50,6 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.PlaceHolderIconDrawable;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconCache.IconLoadRequest;
@@ -65,7 +59,6 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
-import com.android.launcher3.model.data.RemoteActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.views.ActivityContext;
@@ -91,8 +84,6 @@
     private final PointF mTranslationForReorderBounce = new PointF(0, 0);
     private final PointF mTranslationForReorderPreview = new PointF(0, 0);
 
-    private static final int ICON_UPDATE_ANIMATION_DURATION = 375;
-
     private float mScaleForReorderBounce = 1f;
 
     private static final Property<BubbleTextView, Float> DOT_SCALE_PROPERTY
@@ -301,14 +292,6 @@
         verifyHighRes();
     }
 
-    /**
-     * Apply label and tag using a {@link RemoteActionItemInfo}
-     */
-    public void applyFromRemoteActionInfo(RemoteActionItemInfo remoteActionItemInfo) {
-        applyIconAndLabel(remoteActionItemInfo);
-        setTag(remoteActionItemInfo);
-    }
-
     private void applyIconAndLabel(ItemInfoWithIcon info) {
         FastBitmapDrawable iconDrawable = newIcon(getContext(), info);
         mDotParams.color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
@@ -653,14 +636,11 @@
         mDisableRelayout = mIcon != null;
 
         icon.setBounds(0, 0, mIconSize, mIconSize);
-
-        updateIcon(icon);
-
-        // If the current icon is a placeholder color, animate its update.
-        if (mIcon != null && mIcon instanceof PlaceHolderIconDrawable) {
-            animateIconUpdate((PlaceHolderIconDrawable) mIcon, icon);
+        if (mLayoutHorizontal) {
+            setCompoundDrawablesRelative(icon, null, null, null);
+        } else {
+            setCompoundDrawables(null, icon, null, null);
         }
-
         mDisableRelayout = false;
     }
 
@@ -690,8 +670,6 @@
                 mActivity.invalidateParent(info);
             } else if (info instanceof PackageItemInfo) {
                 applyFromItemInfoWithIcon((PackageItemInfo) info);
-            } else if (info instanceof RemoteActionItemInfo) {
-                applyFromRemoteActionInfo((RemoteActionItemInfo) info);
             }
 
             mDisableRelayout = false;
@@ -798,33 +776,4 @@
             ((FastBitmapDrawable) mIcon).setScale(1f);
         }
     }
-
-    private void updateIcon(Drawable newIcon) {
-        if (mLayoutHorizontal) {
-            setCompoundDrawablesRelative(newIcon, null, null, null);
-        } else {
-            setCompoundDrawables(null, newIcon, null, null);
-        }
-    }
-
-    private static void animateIconUpdate(PlaceHolderIconDrawable oldIcon, Drawable newIcon) {
-        int placeholderColor = oldIcon.mPaint.getColor();
-        int originalAlpha = Color.alpha(placeholderColor);
-
-        ValueAnimator iconUpdateAnimation = ValueAnimator.ofInt(originalAlpha, 0);
-        iconUpdateAnimation.setDuration(ICON_UPDATE_ANIMATION_DURATION);
-        iconUpdateAnimation.addUpdateListener(valueAnimator -> {
-            int newAlpha = (int) valueAnimator.getAnimatedValue();
-            int newColor = ColorUtils.setAlphaComponent(placeholderColor, newAlpha);
-
-            newIcon.setColorFilter(new PorterDuffColorFilter(newColor, Mode.SRC_ATOP));
-        });
-        iconUpdateAnimation.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                newIcon.setColorFilter(null);
-            }
-        });
-        iconUpdateAnimation.start();
-    }
 }
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index df005e6..09827d6 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -47,6 +47,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.Thunk;
 
@@ -394,4 +395,6 @@
                 TextUtils.TruncateAt.END);
         return !mText.equals(displayedText);
     }
+
+    public abstract Target getDropTargetForLogging();
 }
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 2809bd5..1cd201f 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -323,8 +323,11 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return mTouchHelper != null
-                || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev));
+        if (mTouchHelper != null
+                || (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
+            return true;
+        }
+        return false;
     }
 
     public void enableHardwareLayer(boolean hasLayer) {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index cc119c9..2857497 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -18,7 +18,8 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_CANCEL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROPPED_ON_REMOVE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNDO;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.UNDO;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -27,19 +28,22 @@
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.ModelWriter;
 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.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.Snackbar;
 
 public class DeleteDropTarget extends ButtonDropTarget {
 
     private final StatsLogManager mStatsLogManager;
 
-    private StatsLogManager.LauncherEvent mLauncherEvent;
+    private int mControlType = ControlType.DEFAULT_CONTROLTYPE;
 
     public DeleteDropTarget(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -111,8 +115,8 @@
      * Set mControlType depending on the drag item.
      */
     private void setControlTypeBasedOnDragSource(ItemInfo item) {
-        mLauncherEvent = item.id != ItemInfo.NO_ID ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
-                : LAUNCHER_ITEM_DROPPED_ON_CANCEL;
+        mControlType = item.id != ItemInfo.NO_ID ? ControlType.REMOVE_TARGET
+                : ControlType.CANCEL_TARGET;
     }
 
     @Override
@@ -123,7 +127,8 @@
         }
         super.onDrop(d, options);
         mStatsLogManager.logger().withInstanceId(d.logInstanceId)
-                .log(mLauncherEvent);
+                .log(mControlType == ControlType.REMOVE_TARGET ? LAUNCHER_ITEM_DROPPED_ON_REMOVE
+                        : LAUNCHER_ITEM_DROPPED_ON_CANCEL);
     }
 
     @Override
@@ -136,7 +141,7 @@
             Runnable onUndoClicked = () -> {
                 mLauncher.setPageToBindSynchronously(itemPage);
                 modelWriter.abortDelete();
-                mLauncher.getStatsLogManager().logger().log(LAUNCHER_UNDO);
+                mLauncher.getUserEventDispatcher().logActionOnControl(TAP, UNDO);
             };
             Snackbar.show(mLauncher, R.string.item_removed, R.string.undo,
                     modelWriter::commitDelete, onUndoClicked);
@@ -156,4 +161,11 @@
         mLauncher.getDragLayer()
                 .announceForAccessibility(getContext().getString(R.string.item_removed));
     }
+
+    @Override
+    public Target getDropTargetForLogging() {
+        Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
+        t.controlType = mControlType;
+        return t;
+    }
 }
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 139d4a8..d3b86de 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -33,8 +33,6 @@
 import android.graphics.drawable.Drawable;
 import android.util.Property;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.graphics.PlaceHolderIconDrawable;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -56,8 +54,6 @@
     protected Bitmap mBitmap;
     protected final int mIconColor;
 
-    @Nullable private ColorFilter mColorFilter;
-
     private boolean mIsPressed;
     private boolean mIsDisabled;
     private float mDisabledAlpha = 1f;
@@ -119,8 +115,7 @@
 
     @Override
     public void setColorFilter(ColorFilter cf) {
-        mColorFilter = cf;
-        updateFilter();
+        // No op
     }
 
     @Override
@@ -270,7 +265,7 @@
      * Updates the paint to reflect the current brightness and saturation.
      */
     protected void updateFilter() {
-        mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mColorFilter);
+        mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : null);
         invalidateSelf();
     }
 
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 6547b53..51f3819 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
@@ -25,10 +27,14 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
-/**
- * View class that represents the bottom row of the home screen.
- */
-public class Hotseat extends CellLayout implements Insettable {
+import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
+import java.util.ArrayList;
+
+public class Hotseat extends CellLayout implements LogContainerProvider, Insettable {
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mHasVerticalHotseat;
@@ -73,6 +79,15 @@
     }
 
     @Override
+    public void fillInLogContainerData(ItemInfo childInfo, Target child,
+            ArrayList<Target> parents) {
+        child.rank = childInfo.rank;
+        child.gridX = childInfo.cellX;
+        child.gridY = childInfo.cellY;
+        parents.add(newContainerTarget(LauncherLogProto.ContainerType.HOTSEAT));
+    }
+
+    @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
         DeviceProfile grid = mActivity.getDeviceProfile();
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index ab8d7a5..4c5224b 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -18,7 +18,6 @@
 
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
-import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
@@ -37,10 +36,11 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONRESUME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ONSTOP;
+import static com.android.launcher3.logging.StatsLogManager.containerTypeToAtomState;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
@@ -70,7 +70,6 @@
 import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Bitmap;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -90,10 +89,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
-import android.widget.ImageView;
 import android.widget.Toast;
 
 import androidx.annotation.CallSuper;
@@ -120,13 +118,13 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.folder.FolderGridOrganizer;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.keyboard.CustomActionsPopup;
 import com.android.launcher3.keyboard.ViewGroupFocusHelper;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.ModelUtils;
@@ -154,6 +152,9 @@
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.ActivityTracker;
 import com.android.launcher3.util.ComponentKey;
@@ -269,8 +270,6 @@
     private static final int APPS_VIEW_ALPHA_CHANNEL_INDEX = 1;
     private static final int SCRIM_VIEW_ALPHA_CHANNEL_INDEX = 0;
 
-    private static final int THEME_CROSS_FADE_ANIMATION_DURATION = 375;
-
     private LauncherAppTransitionManager mAppTransitionManager;
     private Configuration mOldConfig;
 
@@ -402,7 +401,6 @@
 
         inflateRootView(R.layout.launcher);
         setupViews();
-        crossFadeWithPreviousAppearance();
         mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
 
         mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
@@ -479,7 +477,7 @@
                 () -> getStateManager().goToState(NORMAL));
 
         if (Utilities.ATLEAST_R) {
-            getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
+            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
         }
 
         mLifecycleRegistry = new LifecycleRegistry(this);
@@ -557,6 +555,7 @@
     }
 
     private void onIdpChanged(InvariantDeviceProfile idp) {
+        mUserEventDispatcher = null;
 
         initDeviceProfile(idp);
         dispatchDeviceProfileChanged();
@@ -912,7 +911,7 @@
             mOverlayManager.onActivityStopped(this);
         }
 
-        logStopAndResume(false /* isResume */);
+        logStopAndResume(Action.Command.STOP);
         mAppWidgetHost.setListenIfResumed(false);
         NotificationListener.removeNotificationsChangedListener();
     }
@@ -934,7 +933,7 @@
     @Override
     @CallSuper
     protected void onDeferredResumed() {
-        logStopAndResume(true /* isResume */);
+        logStopAndResume(Action.Command.RESUME);
 
         // Process any items that were added while Launcher was away.
         ItemInstallQueue.INSTANCE.get(this)
@@ -951,28 +950,32 @@
 
     protected void handlePendingActivityRequest() { }
 
-    private void logStopAndResume(boolean isResume) {
+    private void logStopAndResume(int command) {
         if (mPendingExecutor != null) return;
         int pageIndex = mWorkspace.isOverlayShown() ? -1 : mWorkspace.getCurrentPage();
-        int statsLogOrdinal = mStateManager.getState().statsLogOrdinal;
+        int containerType = mStateManager.getState().containerType;
 
         StatsLogManager.EventEnum event;
         StatsLogManager.StatsLogger logger = getStatsLogManager().logger();
-        if (isResume) {
+        if (command == Action.Command.RESUME) {
             logger.withSrcState(LAUNCHER_STATE_BACKGROUND)
-                .withDstState(mStateManager.getState().statsLogOrdinal);
+                .withDstState(containerTypeToAtomState(mStateManager.getState().containerType));
             event = LAUNCHER_ONRESUME;
         } else { /* command == Action.Command.STOP */
-            logger.withSrcState(mStateManager.getState().statsLogOrdinal)
+            logger.withSrcState(containerTypeToAtomState(mStateManager.getState().containerType))
                     .withDstState(LAUNCHER_STATE_BACKGROUND);
             event = LAUNCHER_ONSTOP;
         }
 
-        if (statsLogOrdinal == LAUNCHER_STATE_HOME && mWorkspace != null) {
+        if (containerType == ContainerType.WORKSPACE && mWorkspace != null) {
+            getUserEventDispatcher().logActionCommand(command,
+                    containerType, -1, pageIndex);
             logger.withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                     .setWorkspace(
                             LauncherAtom.WorkspaceContainer.newBuilder()
                                     .setPageIndex(pageIndex)).build());
+        } else {
+            getUserEventDispatcher().logActionCommand(command, containerType, -1);
         }
         logger.log(event);
     }
@@ -1362,18 +1365,6 @@
         closeContextMenu();
     }
 
-    @Override
-    public Object onRetainNonConfigurationInstance() {
-        int width = mDragLayer.getWidth();
-        int height = mDragLayer.getHeight();
-
-        if (width <= 0 || height <= 0) {
-            return null;
-        }
-
-        return BitmapRenderer.createHardwareBitmap(width, height, mDragLayer::draw);
-    }
-
     public AllAppsTransitionController getAllAppsController() {
         return mAllAppsController;
     }
@@ -1474,6 +1465,11 @@
             }
 
             // Handle HOME_INTENT
+            UserEventDispatcher ued = getUserEventDispatcher();
+            Target target = newContainerTarget(mStateManager.getState().containerType);
+            target.pageIndex = mWorkspace.getCurrentPage();
+            ued.logActionCommand(Action.Command.HOME_INTENT, target,
+                    newContainerTarget(ContainerType.WORKSPACE));
             hideKeyboard();
 
             if (mLauncherCallbacks != null) {
@@ -2763,40 +2759,4 @@
 
         void onLauncherResume();
     }
-
-    /**
-     * Cross-fades the launcher's updated appearance with its previous appearance.
-     *
-     * This method is used to cross-fade UI updates on activity creation, specifically dark mode
-     * updates.
-     */
-    private void crossFadeWithPreviousAppearance() {
-        Bitmap previousAppearanceBitmap = (Bitmap) getLastNonConfigurationInstance();
-
-        if (previousAppearanceBitmap == null) {
-            return;
-        }
-
-        ImageView crossFadeHelper = new ImageView(this);
-
-        crossFadeHelper.setImageBitmap(previousAppearanceBitmap);
-        crossFadeHelper.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-
-        InsettableFrameLayout.LayoutParams layoutParams = new InsettableFrameLayout.LayoutParams(
-                InsettableFrameLayout.LayoutParams.MATCH_PARENT,
-                InsettableFrameLayout.LayoutParams.MATCH_PARENT);
-
-        layoutParams.ignoreInsets = true;
-
-        crossFadeHelper.setLayoutParams(layoutParams);
-
-        getRootView().addView(crossFadeHelper);
-
-        crossFadeHelper
-                .animate()
-                .setDuration(THEME_CROSS_FADE_ANIMATION_DURATION)
-                .alpha(0f)
-                .withEndAction(() -> getRootView().removeView(crossFadeHelper))
-                .start();
-    }
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index a4181c5..bfe327e 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -105,7 +105,7 @@
         new Handler().post( () -> mInvariantDeviceProfile.verifyConfigChangedInBackground(context));
 
         mInstallSessionTracker = InstallSessionHelper.INSTANCE.get(context)
-                .registerInstallTracker(mModel);
+                .registerInstallTracker(mModel, MODEL_EXECUTOR);
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
         mNotificationDotsObserver =
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index eba0ac9..b6bc500 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -16,7 +16,6 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
@@ -36,6 +35,7 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 import java.util.Arrays;
 
@@ -97,7 +97,7 @@
      * TODO: Create a separate class for NORMAL state.
      */
     public static final LauncherState NORMAL = new LauncherState(NORMAL_STATE_ORDINAL,
-            LAUNCHER_STATE_HOME,
+            ContainerType.WORKSPACE,
             FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HIDE_BACK_BUTTON |
                     FLAG_HAS_SYS_UI_SCRIM) {
         @Override
@@ -126,9 +126,9 @@
     public final int ordinal;
 
     /**
-     * Used for {@link com.android.launcher3.logging.StatsLogManager}
+     * Used for containerType in {@link com.android.launcher3.logging.UserEventDispatcher}
      */
-    public final int statsLogOrdinal;
+    public final int containerType;
 
     /**
      * True if the state has overview panel visible.
@@ -137,8 +137,8 @@
 
     private final int mFlags;
 
-    public LauncherState(int id, int statsLogOrdinal, int flags) {
-        this.statsLogOrdinal = statsLogOrdinal;
+    public LauncherState(int id, int containerType, int flags) {
+        this.containerType = containerType;
         this.mFlags = flags;
         this.overviewUi = (flags & FLAG_OVERVIEW_UI) != 0;
         this.ordinal = id;
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 92b88e6..2df7f5a 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -37,11 +37,14 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.logging.LoggerUtils;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.Themes;
 
@@ -132,6 +135,19 @@
     }
 
     @Override
+    public Target getDropTargetForLogging() {
+        Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
+        if (mCurrentAccessibilityAction == UNINSTALL) {
+            t.controlType = ControlType.UNINSTALL_TARGET;
+        } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+            t.controlType = ControlType.DISMISS_PREDICTION;
+        } else {
+            t.controlType = ControlType.SETTINGS_BUTTON;
+        }
+        return t;
+    }
+
+    @Override
     protected boolean supportsDrop(ItemInfo info) {
         return supportsAccessibilityDrop(info, getViewUnderDrag(info));
     }
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 1bbbb2b..007e5f5 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -25,11 +25,8 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 
-import androidx.annotation.WorkerThread;
-
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.util.Executors;
 
 /**
  * BroadcastReceiver to handle session commit intent.
@@ -41,11 +38,6 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        Executors.MODEL_EXECUTOR.execute(() -> processIntent(context, intent));
-    }
-
-    @WorkerThread
-    private static void processIntent(Context context, Intent intent) {
         if (!isEnabled(context)) {
             // User has decided to not add icons on homescreen.
             return;
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 43ccb79..dea2a8d 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -133,10 +133,6 @@
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
 
-    // An intent extra to indicate the launch source by launcher.
-    public static final String EXTRA_WALLPAPER_LAUNCH_SOURCE =
-            "com.android.wallpaper.LAUNCH_SOURCE";
-
     public static boolean IS_RUNNING_IN_TEST_HARNESS =
                     ActivityManager.isRunningInTestHarness();
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 98328cf..45aaa1b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -85,6 +85,7 @@
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
+import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -96,6 +97,8 @@
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSparseArrayMap;
@@ -1001,6 +1004,8 @@
     public void onOverlayScrollChanged(float scroll) {
         if (Float.compare(scroll, 1f) == 0) {
             if (!mOverlayShown) {
+                mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+                        Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
                 mLauncher.getStatsLogManager().logger()
                         .withSrcState(LAUNCHER_STATE_HOME)
                         .withDstState(LAUNCHER_STATE_HOME)
@@ -1015,16 +1020,20 @@
             // Not announcing the overlay page for accessibility since it announces itself.
         } else if (Float.compare(scroll, 0f) == 0) {
             if (mOverlayShown) {
-                // TODO: this is logged unnecessarily on home gesture.
-                mLauncher.getStatsLogManager().logger()
-                        .withSrcState(LAUNCHER_STATE_HOME)
-                        .withDstState(LAUNCHER_STATE_HOME)
-                        .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
-                                .setWorkspace(
-                                        LauncherAtom.WorkspaceContainer.newBuilder()
-                                                .setPageIndex(-1))
-                                .build())
-                        .log(LAUNCHER_SWIPERIGHT);
+                UserEventDispatcher ued = mLauncher.getUserEventDispatcher();
+                if (!ued.isPreviousHomeGesture()) {
+                    mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+                        Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
+                    mLauncher.getStatsLogManager().logger()
+                            .withSrcState(LAUNCHER_STATE_HOME)
+                            .withDstState(LAUNCHER_STATE_HOME)
+                            .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+                                    .setWorkspace(
+                                            LauncherAtom.WorkspaceContainer.newBuilder()
+                                                    .setPageIndex(-1))
+                                    .build())
+                            .log(LAUNCHER_SWIPERIGHT);
+                }
             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
                 // accessibility since default announcements were disabled while in overscroll
@@ -1115,8 +1124,12 @@
     protected void notifyPageSwitchListener(int prevPage) {
         super.notifyPageSwitchListener(prevPage);
         if (prevPage != mCurrentPage) {
+            int swipeDirection = (prevPage < mCurrentPage)
+                    ? Action.Direction.RIGHT : Action.Direction.LEFT;
             StatsLogManager.EventEnum event = (prevPage < mCurrentPage)
                     ? LAUNCHER_SWIPERIGHT : LAUNCHER_SWIPELEFT;
+            mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
+                    swipeDirection, ContainerType.WORKSPACE, prevPage);
             mLauncher.getStatsLogManager().logger()
                     .withSrcState(LAUNCHER_STATE_HOME)
                     .withDstState(LAUNCHER_STATE_HOME)
@@ -1163,6 +1176,13 @@
     }
 
     @Override
+    protected void determineScrollingStart(MotionEvent ev, float touchSlopScale) {
+        if (!isSwitchingState()) {
+            super.determineScrollingStart(ev, touchSlopScale);
+        }
+    }
+
+    @Override
     public void announceForAccessibility(CharSequence text) {
         // Don't announce if apps is on top of us.
         if (!mLauncher.isInState(ALL_APPS)) {
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index d6302ce..ea887cc 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -130,16 +130,12 @@
         }
 
         child.setHapticFeedbackEnabled(false);
-        child.setOnLongClickListener(getWorkspaceChildOnLongClickListener());
+        child.setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
         if (child instanceof DropTarget) {
             onAddDropTarget((DropTarget) child);
         }
     }
 
-    default View.OnLongClickListener getWorkspaceChildOnLongClickListener() {
-        return ItemLongClickListener.INSTANCE_WORKSPACE;
-    }
-
     Hotseat getHotseat();
 
     CellLayout getScreenWithId(int screenId);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 8bc8e53..d1340fa 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -92,14 +92,10 @@
 
     public static final int VIEW_TYPE_SEARCH_SLICE = 1 << 9;
 
-    public static final int VIEW_TYPE_SEARCH_ICON_ROW = 1 << 10;
+    public static final int VIEW_TYPE_SEARCH_SHORTCUT = 1 << 10;
 
     public static final int VIEW_TYPE_SEARCH_PEOPLE = 1 << 11;
 
-    public static final int VIEW_TYPE_SEARCH_THUMBNAIL = 1 << 12;
-
-    public static final int VIEW_TYPE_SEARCH_SUGGEST = 1 << 13;
-
     // 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;
@@ -190,9 +186,7 @@
                     || viewType == VIEW_TYPE_SEARCH_SLICE
                     || viewType == VIEW_TYPE_SEARCH_ROW
                     || viewType == VIEW_TYPE_SEARCH_PEOPLE
-                    || viewType == VIEW_TYPE_SEARCH_THUMBNAIL
-                    || viewType == VIEW_TYPE_SEARCH_ICON_ROW
-                    || viewType == VIEW_TYPE_SEARCH_SUGGEST;
+                    || viewType == VIEW_TYPE_SEARCH_SHORTCUT;
         }
     }
 
@@ -203,7 +197,6 @@
      */
     public static class AdapterItemWithPayload<T> extends AdapterItem {
         private T mPayload;
-        private String mSearchSessionId;
         private AllAppsSearchPlugin mPlugin;
         private IntConsumer mSelectionHandler;
 
@@ -225,14 +218,6 @@
             mSelectionHandler = runnable;
         }
 
-        public void setSearchSessionId(String searchSessionId) {
-            mSearchSessionId = searchSessionId;
-        }
-
-        public String getSearchSessionId() {
-            return mSearchSessionId;
-        }
-
         public IntConsumer getSelectionHandler() {
             return mSelectionHandler;
         }
@@ -240,8 +225,6 @@
         public T getPayload() {
             return mPayload;
         }
-
-
     }
 
     /**
@@ -324,21 +307,15 @@
 
         @Override
         public int getSpanSize(int position) {
-            int viewType = mApps.getAdapterItems().get(position).viewType;
-            if (isIconViewType(viewType)) {
-                return 1 * SPAN_MULTIPLIER;
-            } else if (viewType == VIEW_TYPE_SEARCH_THUMBNAIL) {
-                return mAppsPerRow;
+            if (isIconViewType(mApps.getAdapterItems().get(position).viewType)) {
+                return 1;
             } else {
                 // Section breaks span the full width
-                return mAppsPerRow * SPAN_MULTIPLIER;
+                return mAppsPerRow;
             }
         }
     }
 
-    // multiplier to support adapter item column count that is not mAppsPerRow.
-    public static final int SPAN_MULTIPLIER = 3;
-
     private final BaseDraggingActivity mLauncher;
     private final LayoutInflater mLayoutInflater;
     private final AlphabeticalAppsList mApps;
@@ -375,7 +352,7 @@
 
     public void setAppsPerRow(int appsPerRow) {
         mAppsPerRow = appsPerRow;
-        mGridLayoutMgr.setSpanCount(mAppsPerRow * SPAN_MULTIPLIER);
+        mGridLayoutMgr.setSpanCount(mAppsPerRow);
     }
 
     /**
@@ -461,18 +438,12 @@
             case VIEW_TYPE_SEARCH_SLICE:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_slice, parent, false));
-            case VIEW_TYPE_SEARCH_ICON_ROW:
+            case VIEW_TYPE_SEARCH_SHORTCUT:
                 return new ViewHolder(mLayoutInflater.inflate(
-                        R.layout.search_result_icon_row, parent, false));
+                        R.layout.search_result_shortcut, parent, false));
             case VIEW_TYPE_SEARCH_PEOPLE:
                 return new ViewHolder(mLayoutInflater.inflate(
                         R.layout.search_result_people_item, parent, false));
-            case VIEW_TYPE_SEARCH_THUMBNAIL:
-                return new ViewHolder(mLayoutInflater.inflate(
-                        R.layout.search_result_thumbnail, parent, false));
-            case VIEW_TYPE_SEARCH_SUGGEST:
-                return new ViewHolder(mLayoutInflater.inflate(
-                        R.layout.search_result_suggest, parent, false));
             default:
                 throw new RuntimeException("Unexpected view type");
         }
@@ -493,27 +464,24 @@
                 //TODO: replace with custom TopHitBubbleTextView with support for both shortcut
                 // and apps
                 if (adapterItem instanceof AdapterItemWithPayload) {
-                    AdapterItemWithPayload item = (AdapterItemWithPayload) adapterItem;
-                    item.setSelectionHandler(type -> {
+                    AdapterItemWithPayload withPayload = (AdapterItemWithPayload) adapterItem;
+                    IntConsumer selectionHandler = type -> {
                         SearchTargetEvent e = new SearchTargetEvent(SearchTarget.ItemType.APP,
-                                type, item.position, item.getSearchSessionId());
+                                type);
                         e.bundle = HeroSearchResultView.getAppBundle(info);
-                        if (item.getPlugin() != null) {
-                            item.getPlugin().notifySearchTargetEvent(e);
+                        if (withPayload.getPlugin() != null) {
+                            withPayload.getPlugin().notifySearchTargetEvent(e);
                         }
-                    });
+                    };
                     icon.setOnClickListener(view -> {
-                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
+                        selectionHandler.accept(SearchTargetEvent.SELECT);
                         mOnIconClickListener.onClick(view);
                     });
                     icon.setOnLongClickListener(view -> {
-                        item.getSelectionHandler().accept(SearchTargetEvent.SELECT);
+                        selectionHandler.accept(SearchTargetEvent.LONG_PRESS);
                         return mOnIconLongClickListener.onLongClick(view);
                     });
-                }
-                else {
-                    icon.setOnClickListener(mOnIconClickListener);
-                    icon.setOnLongClickListener(mOnIconLongClickListener);
+                    withPayload.setSelectionHandler(selectionHandler);
                 }
                 break;
             case VIEW_TYPE_EMPTY_SEARCH:
@@ -532,22 +500,20 @@
                 break;
             case VIEW_TYPE_SEARCH_SLICE:
                 SliceView sliceView = (SliceView) holder.itemView;
-                AdapterItemWithPayload<Uri> slicePayload =
+                AdapterItemWithPayload<Uri> item =
                         (AdapterItemWithPayload<Uri>) mApps.getAdapterItems().get(position);
                 sliceView.setOnSliceActionListener((info1, s) -> {
-                    if (slicePayload.getPlugin() != null) {
+                    if (item.getPlugin() != null) {
                         SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
                                 SearchTarget.ItemType.SETTINGS_SLICE,
-                                SearchTargetEvent.CHILD_SELECT, slicePayload.position,
-                                slicePayload.getSearchSessionId());
+                                SearchTargetEvent.CHILD_SELECT);
                         searchTargetEvent.bundle = new Bundle();
-                        searchTargetEvent.bundle.putParcelable("uri", slicePayload.getPayload());
-                        slicePayload.getPlugin().notifySearchTargetEvent(searchTargetEvent);
+                        searchTargetEvent.bundle.putParcelable("uri", item.getPayload());
+                        item.getPlugin().notifySearchTargetEvent(searchTargetEvent);
                     }
                 });
                 try {
-                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher,
-                            slicePayload.getPayload());
+                    LiveData<Slice> liveData = SliceLiveData.fromUri(mLauncher, item.getPayload());
                     liveData.observe((Launcher) mLauncher, sliceView);
                     sliceView.setTag(liveData);
                 } catch (Exception ignored) {
@@ -557,14 +523,11 @@
             case VIEW_TYPE_SEARCH_ROW_WITH_BUTTON:
             case VIEW_TYPE_SEARCH_HERO_APP:
             case VIEW_TYPE_SEARCH_ROW:
-            case VIEW_TYPE_SEARCH_ICON_ROW:
+            case VIEW_TYPE_SEARCH_SHORTCUT:
             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);
+                payloadResultView.applyAdapterInfo(
+                        (AdapterItemWithPayload) mApps.getAdapterItems().get(position));
                 break;
             case VIEW_TYPE_ALL_APPS_DIVIDER:
                 // nothing to do
@@ -578,8 +541,8 @@
         if (!FeatureFlags.ENABLE_DEVICE_SEARCH.get()) return;
         if (holder.itemView instanceof BubbleTextView) {
             BubbleTextView icon = (BubbleTextView) holder.itemView;
-            icon.setOnClickListener(null);
-            icon.setOnLongClickListener(null);
+            icon.setOnClickListener(mOnIconClickListener);
+            icon.setOnLongClickListener(mOnIconLongClickListener);
         } else if (holder.itemView instanceof SliceView) {
             SliceView sliceView = (SliceView) holder.itemView;
             sliceView.setOnSliceActionListener(null);
@@ -590,6 +553,7 @@
         }
     }
 
+
     @Override
     public boolean onFailedToRecycleView(ViewHolder holder) {
         // Always recycle and we will reset the view when it is bound
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index 72b6d94..13a93ff 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -19,6 +19,8 @@
 import static android.view.View.MeasureSpec.UNSPECIFIED;
 import static android.view.View.MeasureSpec.makeMeasureSpec;
 
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
@@ -36,6 +38,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
+import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
 import java.util.ArrayList;
@@ -44,7 +50,7 @@
 /**
  * A RecyclerView with custom fast scroll support for the all apps view.
  */
-public class AllAppsRecyclerView extends BaseRecyclerView {
+public class AllAppsRecyclerView extends BaseRecyclerView implements LogContainerProvider {
 
     private AlphabeticalAppsList mApps;
     private final int mNumAppsPerRow;
@@ -170,6 +176,13 @@
         mAutoSizedOverlays.clear();
     }
 
+    @Override
+    public void fillInLogContainerData(ItemInfo childInfo, Target child,
+            ArrayList<Target> parents) {
+        parents.add(newContainerTarget(
+                getApps().hasFilter() ? ContainerType.SEARCHRESULT : ContainerType.ALLAPPS));
+    }
+
     public void onSearchResultsChanged() {
         // Always scroll the view to the top so the user can see the changed results
         scrollToTop();
diff --git a/src/com/android/launcher3/allapps/DiscoveryBounce.java b/src/com/android/launcher3/allapps/DiscoveryBounce.java
index 0005db8..14595ca 100644
--- a/src/com/android/launcher3/allapps/DiscoveryBounce.java
+++ b/src/com/android/launcher3/allapps/DiscoveryBounce.java
@@ -18,6 +18,8 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.HOTSEAT;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType.PREDICTION;
 
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
@@ -118,10 +120,10 @@
         return (type & TYPE_DISCOVERY_BOUNCE) != 0;
     }
 
-    private void show() {
+    private void show(int containerType) {
         mIsOpen = true;
         mLauncher.getDragLayer().addView(this);
-        // TODO: add WW log for discovery bounce tip show event.
+        mLauncher.getUserEventDispatcher().logActionBounceTip(containerType);
     }
 
     public static void showForHomeIfNeeded(Launcher launcher) {
@@ -144,7 +146,7 @@
         }
         onboardingPrefs.incrementEventCount(OnboardingPrefs.HOME_BOUNCE_COUNT);
 
-        new DiscoveryBounce(launcher, 0).show();
+        new DiscoveryBounce(launcher, 0).show(HOTSEAT);
     }
 
     public static void showForOverviewIfNeeded(Launcher launcher,
@@ -177,7 +179,7 @@
         onboardingPrefs.incrementEventCount(OnboardingPrefs.SHELF_BOUNCE_COUNT);
 
         new DiscoveryBounce(launcher, (1 - OVERVIEW.getVerticalProgress(launcher)))
-                .show();
+                .show(PREDICTION);
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index d7fa5bc..3320189 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -35,8 +35,6 @@
 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;
@@ -215,44 +213,6 @@
         /**
          * Updates View using Adapter's payload
          */
-
-        default void setup(AdapterItemWithPayload<T> adapterItemWithPayload) {
-            Object[] targetInfo = getTargetInfo();
-            if (targetInfo != null) {
-                targetInfo[0] = adapterItemWithPayload.getSearchSessionId();
-                targetInfo[1] = adapterItemWithPayload.position;
-            }
-            applyAdapterInfo(adapterItemWithPayload);
-        }
-
         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);
-        }
     }
-
-
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 6dd316e..5362575 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -73,9 +73,9 @@
         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
     }
 
-    public void finish(boolean isSuccess) {
+    public void finish(boolean isSuccess, int logAction) {
         for (Consumer<EndState> listeners : mEndListeners) {
-            listeners.accept(new EndState(isSuccess));
+            listeners.accept(new EndState(isSuccess, logAction));
         }
         mEndListeners.clear();
     }
@@ -164,7 +164,7 @@
 
     /**
      * Add a listener of receiving the end state.
-     * Note that the listeners are called as a result of calling {@link #finish(boolean)}
+     * Note that the listeners are called as a result of calling {@link #finish(boolean, int)}
      * and not automatically
      */
     public void addEndListener(Consumer<EndState> listener) {
@@ -173,9 +173,11 @@
 
     public static class EndState {
         public boolean isSuccess;
+        public int logAction;
 
-        public EndState(boolean isSuccess) {
+        public EndState(boolean isSuccess, int logAction) {
             this.isSuccess = isSuccess;
+            this.logAction = logAction;
         }
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/AddItemActivity.java b/src/com/android/launcher3/dragndrop/AddItemActivity.java
index 42e247a..2d625c5 100644
--- a/src/com/android/launcher3/dragndrop/AddItemActivity.java
+++ b/src/com/android/launcher3/dragndrop/AddItemActivity.java
@@ -16,11 +16,10 @@
 
 package com.android.launcher3.dragndrop;
 
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_BACK;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_EXTERNAL_ITEM_START;
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.annotation.TargetApi;
@@ -50,10 +49,11 @@
 import com.android.launcher3.LauncherAppWidgetHost;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.PinRequestHelper;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -125,7 +125,7 @@
         // savedInstanceState is null when the activity is created the first time (i.e., avoids
         // duplicate logging during rotation)
         if (savedInstanceState == null) {
-            logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_START);
+            logCommand(Action.Command.ENTRY);
         }
     }
 
@@ -178,7 +178,6 @@
         startActivity(homeIntent,
                 ActivityOptions.makeCustomAnimation(this, 0, android.R.anim.fade_out)
                         .toBundle());
-        logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED);
         mFinishOnPause = true;
         return false;
     }
@@ -241,7 +240,7 @@
      * Called when the cancel button is clicked.
      */
     public void onCancelClick(View v) {
-        logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED);
+        logCommand(Action.Command.CANCEL);
         finish();
     }
 
@@ -251,7 +250,7 @@
     public void onPlaceAutomaticallyClick(View v) {
         if (mRequest.getRequestType() == PinItemRequest.REQUEST_TYPE_SHORTCUT) {
             ItemInstallQueue.INSTANCE.get(this).queueItem(mRequest.getShortcutInfo());
-            logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY);
+            logCommand(Action.Command.CONFIRM);
             mRequest.accept();
             finish();
             return;
@@ -275,13 +274,13 @@
                 .queueItem(mRequest.getAppWidgetProviderInfo(this), widgetId);
         mWidgetOptions.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
         mRequest.accept(mWidgetOptions);
-        logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY);
+        logCommand(Action.Command.CONFIRM);
         finish();
     }
 
     @Override
     public void onBackPressed() {
-        logCommand(LAUNCHER_ADD_EXTERNAL_ITEM_BACK);
+        logCommand(Action.Command.BACK);
         super.onBackPressed();
     }
 
@@ -321,7 +320,10 @@
         throw new UnsupportedOperationException();
     }
 
-    private void logCommand(StatsLogManager.EventEnum command) {
-        getStatsLogManager().logger().log(command);
+    private void logCommand(int command) {
+        getUserEventDispatcher().dispatchUserEvent(newLauncherEvent(
+                newCommandAction(command),
+                newItemTarget(mWidgetCell.getWidgetView(), mInstantAppResolver),
+                newContainerTarget(ContainerType.PINITEM)), null);
     }
 }
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 1cfe6ac..ef666f0 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -206,6 +206,7 @@
         }
 
         handleMoveEvent(mLastTouch.x, mLastTouch.y);
+        mLauncher.getUserEventDispatcher().resetActionDurationMillis();
 
         if (!mLauncher.isTouchInProgress() && options.simulatedDndStartPoint == null) {
             // If it is an internal drag and the touch is already complete, cancel immediately
@@ -543,6 +544,7 @@
             }
         }
         final View dropTargetAsView = dropTarget instanceof View ? (View) dropTarget : null;
+        mLauncher.getUserEventDispatcher().logDragNDrop(mDragObject, dropTargetAsView);
         dispatchDropComplete(dropTargetAsView, accepted);
     }
 
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 63fa391..281598a 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 import static com.android.launcher3.config.FeatureFlags.ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_CONVERTED_TO_ICON;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_LABEL_UPDATED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED;
@@ -94,6 +95,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ClipPathView;
@@ -597,6 +599,15 @@
      * is played.
      */
     private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
+        animateOpen(items, pageNo, false);
+    }
+
+    /**
+     * Opens the user folder described by the specified tag. The opening of the folder
+     * is animated relative to the specified View. If the View is null, no animation
+     * is played.
+     */
+    private void animateOpen(List<WorkspaceItemInfo> items, int pageNo, boolean skipUserEventLog) {
         Folder openFolder = getOpen(mLauncher);
         if (openFolder != null && openFolder != this) {
             // Close any open folder before opening a folder.
@@ -646,6 +657,14 @@
                 mState = STATE_OPEN;
                 announceAccessibilityChanges();
 
+                if (!skipUserEventLog) {
+                    mLauncher.getUserEventDispatcher().logActionOnItem(
+                            LauncherLogProto.Action.Touch.TAP,
+                            LauncherLogProto.Action.Direction.NONE,
+                            LauncherLogProto.ItemType.FOLDER_ICON, mInfo.cellX, mInfo.cellY);
+                }
+
+
                 mContent.setFocusOnFirstChild();
             }
         });
@@ -1494,6 +1513,7 @@
                 }
 
                 statsLogger.log(LAUNCHER_FOLDER_LABEL_UPDATED);
+                logFolderLabelState(mFromLabelState, toLabelState);
                 mFolderName.dispatchBackKey();
             }
         }
@@ -1624,7 +1644,8 @@
                         return true;
                     }
                 } else {
-                    // TODO: add ww log if need to gather tap outside to close folder
+                    mLauncher.getUserEventDispatcher().logActionTapOutside(
+                            newContainerTarget(LauncherLogProto.ContainerType.FOLDER));
                     close(true);
                     return true;
                 }
@@ -1659,6 +1680,17 @@
         return mContent;
     }
 
+    /**
+     * Logs current folder label info.
+     *
+     * @deprecated This method is only used for log validation and soon will be removed.
+     */
+    @Deprecated
+    public void logFolderLabelState(FromState fromState, ToState toState) {
+        mLauncher.getUserEventDispatcher()
+                .logLauncherEvent(mInfo.getFolderLabelStateLauncherEvent(fromState, toState));
+    }
+
     /** Returns the height of the current folder's bottom edge from the bottom of the screen. */
     private int getHeightFromBottom() {
         DragLayer.LayoutParams layoutParams = (DragLayer.LayoutParams) getLayoutParams();
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3296eed..32d061c 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -478,6 +478,7 @@
                 // event is assumed to be folder creation on the server side.
                 .withEditText(newTitle.toString())
                 .log(LAUNCHER_FOLDER_AUTO_LABELED);
+        mFolder.logFolderLabelState(fromState, ToState.TO_SUGGESTION0);
     }
 
 
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index effb3a4..cd84c96 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -513,7 +513,8 @@
         }
 
         // Setup search view
-        SearchUiManager searchUiManager = mRootView.findViewById(R.id.search_container_all_apps);
+        SearchUiManager searchUiManager =
+                mRootView.findViewById(R.id.search_container_all_apps);
         mRootView.findViewById(R.id.apps_view).setTranslationY(
                 mDp.heightPx - searchUiManager.getScrollRangeDelta(mInsets));
         ViewGroup searchView = (ViewGroup) searchUiManager;
diff --git a/src/com/android/launcher3/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
new file mode 100644
index 0000000..cd4f034
--- /dev/null
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 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.logging;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.view.View;
+
+import com.android.launcher3.ButtonDropTarget;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogExtensions.TargetExtension;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Helper methods for logging.
+ */
+public class LoggerUtils {
+    private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
+    private static final String UNKNOWN = "UNKNOWN";
+    private static final int DEFAULT_PREDICTED_RANK = 10000;
+    private static final String DELIMITER_DOT = "\\.";
+
+    public static String getFieldName(int value, Class c) {
+        SparseArray<String> cache;
+        synchronized (sNameCache) {
+            cache = sNameCache.get(c);
+            if (cache == null) {
+                cache = new SparseArray<>();
+                for (Field f : c.getDeclaredFields()) {
+                    if (f.getType() == int.class && Modifier.isStatic(f.getModifiers())) {
+                        try {
+                            f.setAccessible(true);
+                            cache.put(f.getInt(null), f.getName());
+                        } catch (IllegalAccessException e) {
+                            // Ignore
+                        }
+                    }
+                }
+                sNameCache.put(c, cache);
+            }
+        }
+        String result = cache.get(value);
+        return result != null ? result : UNKNOWN;
+    }
+
+    public static Target newItemTarget(int itemType) {
+        Target t = newTarget(Target.Type.ITEM);
+        t.itemType = itemType;
+        return t;
+    }
+
+    public static Target newItemTarget(View v, InstantAppResolver instantAppResolver) {
+        return (v != null) && (v.getTag() instanceof ItemInfo)
+                ? newItemTarget((ItemInfo) v.getTag(), instantAppResolver)
+                : newTarget(Target.Type.ITEM);
+    }
+
+    public static Target newItemTarget(ItemInfo info, InstantAppResolver instantAppResolver) {
+        Target t = newTarget(Target.Type.ITEM);
+        switch (info.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
+                t.itemType = (instantAppResolver != null && info instanceof AppInfo
+                        && instantAppResolver.isInstantApp(((AppInfo) info)))
+                        ? ItemType.WEB_APP
+                        : ItemType.APP_ICON;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                t.itemType = ItemType.SHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                t.itemType = ItemType.FOLDER_ICON;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                t.itemType = ItemType.WIDGET;
+                break;
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                t.itemType = ItemType.DEEPSHORTCUT;
+                t.predictedRank = DEFAULT_PREDICTED_RANK;
+                break;
+        }
+        return t;
+    }
+
+    public static Target newDropTarget(View v) {
+        if (!(v instanceof ButtonDropTarget)) {
+            return newTarget(Target.Type.CONTAINER);
+        }
+        if (v instanceof ButtonDropTarget) {
+            return ((ButtonDropTarget) v).getDropTargetForLogging();
+        }
+        return newTarget(Target.Type.CONTROL);
+    }
+
+    public static Target newTarget(int targetType, TargetExtension extension) {
+        Target t = new Target();
+        t.type = targetType;
+        t.extension = extension;
+        return t;
+    }
+
+    public static Target newTarget(int targetType) {
+        Target t = new Target();
+        t.type = targetType;
+        return t;
+    }
+
+    public static Target newControlTarget(int controlType) {
+        Target t = newTarget(Target.Type.CONTROL);
+        t.controlType = controlType;
+        return t;
+    }
+
+    public static Target newContainerTarget(int containerType) {
+        Target t = newTarget(Target.Type.CONTAINER);
+        t.containerType = containerType;
+        return t;
+    }
+
+    public static Action newAction(int type) {
+        Action a = new Action();
+        a.type = type;
+        return a;
+    }
+
+    public static Action newCommandAction(int command) {
+        Action a = newAction(Action.Type.COMMAND);
+        a.command = command;
+        return a;
+    }
+
+    public static Action newTouchAction(int touch) {
+        Action a = newAction(Action.Type.TOUCH);
+        a.touch = touch;
+        return a;
+    }
+
+    public static LauncherEvent newLauncherEvent(Action action, Target... srcTargets) {
+        LauncherEvent event = new LauncherEvent();
+        event.srcTarget = srcTargets;
+        event.action = action;
+        return event;
+    }
+
+    /**
+     * Creates LauncherEvent using Action and ArrayList of Targets
+     */
+    public static LauncherEvent newLauncherEvent(Action action, ArrayList<Target> targets) {
+        Target[] targetsArray = new Target[targets.size()];
+        targets.toArray(targetsArray);
+        return newLauncherEvent(action, targetsArray);
+    }
+
+    /**
+     * String conversion for only the helpful parts of {@link Object#toString()} method
+     * @param stringToExtract "foo.bar.baz.MyObject@1234"
+     * @return "MyObject@1234"
+     */
+    public static String extractObjectNameAndAddress(String stringToExtract) {
+        String[] superStringParts = stringToExtract.split(DELIMITER_DOT);
+        if (superStringParts.length == 0) {
+            return "";
+        }
+        return superStringParts[superStringParts.length - 1];
+    }
+}
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2c5bf32..ec1c3ef 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.LauncherLogProto;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.List;
@@ -50,22 +51,40 @@
     public static final int LAUNCHER_STATE_UNCHANGED = 5;
 
     /**
-     * Returns event enum based on the two state transition information when swipe
+     * Returns proper launcher state enum for {@link StatsLogManager}(to be removed during
+     * UserEventDispatcher cleanup)
+     */
+    public static int containerTypeToAtomState(int containerType) {
+        switch (containerType) {
+            case LauncherLogProto.ContainerType.ALLAPPS_VALUE:
+                return LAUNCHER_STATE_ALLAPPS;
+            case LauncherLogProto.ContainerType.OVERVIEW_VALUE:
+                return LAUNCHER_STATE_OVERVIEW;
+            case LauncherLogProto.ContainerType.WORKSPACE_VALUE:
+                return LAUNCHER_STATE_HOME;
+            case LauncherLogProto.ContainerType.APP_VALUE:
+                return LAUNCHER_STATE_BACKGROUND;
+        }
+        return LAUNCHER_STATE_UNSPECIFIED;
+    }
+
+    /**
+     * Returns event enum based on the two {@link ContainerType} transition information when swipe
      * gesture happens(to be removed during UserEventDispatcher cleanup).
      */
-    public static EventEnum getLauncherAtomEvent(int startState,
-            int targetState, EventEnum fallbackEvent) {
-        if (startState == LAUNCHER_STATE_HOME
-                && targetState == LAUNCHER_STATE_HOME) {
+    public static EventEnum getLauncherAtomEvent(int startContainerType,
+            int targetContainerType, EventEnum fallbackEvent) {
+        if (startContainerType == LauncherLogProto.ContainerType.WORKSPACE.getNumber()
+                && targetContainerType == LauncherLogProto.ContainerType.WORKSPACE.getNumber()) {
             return LAUNCHER_HOME_GESTURE;
-        } else if (startState != LAUNCHER_STATE_OVERVIEW
-                && targetState == LAUNCHER_STATE_OVERVIEW) {
+        } else if (startContainerType != LauncherLogProto.ContainerType.TASKSWITCHER.getNumber()
+                && targetContainerType == LauncherLogProto.ContainerType.TASKSWITCHER.getNumber()) {
             return LAUNCHER_OVERVIEW_GESTURE;
-        } else if (startState != LAUNCHER_STATE_ALLAPPS
-                && targetState == LAUNCHER_STATE_ALLAPPS) {
+        } else if (startContainerType != LauncherLogProto.ContainerType.ALLAPPS.getNumber()
+                && targetContainerType == LauncherLogProto.ContainerType.ALLAPPS.getNumber()) {
             return LAUNCHER_ALLAPPS_OPEN_UP;
-        } else if (startState == LAUNCHER_STATE_ALLAPPS
-                && targetState != LAUNCHER_STATE_ALLAPPS) {
+        } else if (startContainerType == LauncherLogProto.ContainerType.ALLAPPS.getNumber()
+                && targetContainerType != LauncherLogProto.ContainerType.ALLAPPS.getNumber()) {
             return LAUNCHER_ALLAPPS_CLOSE_DOWN;
         }
         return fallbackEvent; // TODO fix
@@ -303,38 +322,7 @@
         LAUNCHER_FOLDER_CONVERTED_TO_ICON(628),
 
         @UiEvent(doc = "A hotseat prediction item was pinned")
-        LAUNCHER_HOTSEAT_PREDICTION_PINNED(629),
-
-        @UiEvent(doc = "Activity to add external item was started")
-        LAUNCHER_ADD_EXTERNAL_ITEM_START(641),
-
-        @UiEvent(doc = "Activity to add external item was cancelled")
-        LAUNCHER_ADD_EXTERNAL_ITEM_CANCELLED(642),
-
-        @UiEvent(doc = "Activity to add external item was backed out")
-        LAUNCHER_ADD_EXTERNAL_ITEM_BACK(643),
-
-        @UiEvent(doc = "Item was placed automatically in external item addition flow")
-        LAUNCHER_ADD_EXTERNAL_ITEM_PLACED_AUTOMATICALLY(644),
-
-        @UiEvent(doc = "Item was dragged in external item addition flow")
-        LAUNCHER_ADD_EXTERNAL_ITEM_DRAGGED(645),
-
-        @UiEvent(doc = "Undo event was tapped.")
-        LAUNCHER_UNDO(648),
-
-        @UiEvent(doc = "Task switcher clear all target was tapped.")
-        LAUNCHER_TASK_CLEAR_ALL(649),
-
-        @UiEvent(doc = "Task preview was long pressed.")
-        LAUNCHER_TASK_PREVIEW_LONGPRESS(650),
-
-        @UiEvent(doc = "User swiped down on workspace (triggering noti shade to open).")
-        LAUNCHER_SWIPE_DOWN_WORKSPACE_NOTISHADE_OPEN(651),
-
-        @UiEvent(doc = "Notification dismissed by swiping right.")
-        LAUNCHER_NOTIFICATION_DISMISSED(652),
-        ;
+        LAUNCHER_HOTSEAT_PREDICTION_PINNED(629);
 
         // ADD MORE
 
diff --git a/src/com/android/launcher3/logging/StatsLogUtils.java b/src/com/android/launcher3/logging/StatsLogUtils.java
new file mode 100644
index 0000000..a5cc7ea
--- /dev/null
+++ b/src/com/android/launcher3/logging/StatsLogUtils.java
@@ -0,0 +1,49 @@
+package com.android.launcher3.logging;
+
+import android.view.View;
+import android.view.ViewParent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
+import java.util.ArrayList;
+
+public class StatsLogUtils {
+    private final static int MAXIMUM_VIEW_HIERARCHY_LEVEL = 5;
+
+    /**
+     * Implemented by containers to provide a container source for a given child.
+     */
+    public interface LogContainerProvider {
+
+        /**
+         * Populates parent container targets for an item
+         */
+        void fillInLogContainerData(ItemInfo childInfo, Target child, ArrayList<Target> parents);
+    }
+
+    /**
+     * Recursively finds the parent of the given child which implements IconLogInfoProvider
+     */
+    public static LogContainerProvider getLaunchProviderRecursive(@Nullable View v) {
+        ViewParent parent;
+        if (v != null) {
+            parent = v.getParent();
+        } else {
+            return null;
+        }
+
+        // Optimization to only check up to 5 parents.
+        int count = MAXIMUM_VIEW_HIERARCHY_LEVEL;
+        while (parent != null && count-- > 0) {
+            if (parent instanceof LogContainerProvider) {
+                return (LogContainerProvider) parent;
+            } else {
+                parent = parent.getParent();
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
new file mode 100644
index 0000000..a40cc26
--- /dev/null
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2012 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.logging;
+
+import static com.android.launcher3.logging.LoggerUtils.newAction;
+import static com.android.launcher3.logging.LoggerUtils.newCommandAction;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
+import static com.android.launcher3.logging.LoggerUtils.newDropTarget;
+import static com.android.launcher3.logging.LoggerUtils.newItemTarget;
+import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
+import static com.android.launcher3.logging.LoggerUtils.newTarget;
+import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
+
+import static java.util.Optional.ofNullable;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DropTarget;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.LauncherLogProto;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.LogConfig;
+import com.android.launcher3.util.ResourceBasedOverride;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+/**
+ * Manages the creation of {@link LauncherEvent}.
+ * To debug this class, execute following command before side loading a new apk.
+ * <p>
+ * $ adb shell setprop log.tag.UserEvent VERBOSE
+ */
+public class UserEventDispatcher implements ResourceBasedOverride {
+
+    private static final String TAG = "UserEvent";
+    private static final boolean IS_VERBOSE = Utilities.isPropertyEnabled(LogConfig.USEREVENT);
+    private static final String UUID_STORAGE = "uuid";
+
+    /**
+     * A factory method for UserEventDispatcher
+     */
+    public static UserEventDispatcher newInstance(Context context) {
+        SharedPreferences sharedPrefs = Utilities.getDevicePrefs(context);
+        String uuidStr = sharedPrefs.getString(UUID_STORAGE, null);
+        if (uuidStr == null) {
+            uuidStr = UUID.randomUUID().toString();
+            sharedPrefs.edit().putString(UUID_STORAGE, uuidStr).apply();
+        }
+        UserEventDispatcher ued = Overrides.getObject(UserEventDispatcher.class,
+                context.getApplicationContext(), R.string.user_event_dispatcher_class);
+        ued.mUuidStr = uuidStr;
+        ued.mInstantAppResolver = InstantAppResolver.newInstance(context);
+        return ued;
+    }
+
+
+    /**
+     * Fills in the container data on the given event if the given view is not null.
+     *
+     * @return whether container data was added.
+     */
+    private boolean fillLogContainer(@Nullable View v, Target child,
+            @Nullable ArrayList<Target> targets) {
+        LogContainerProvider firstParent = StatsLogUtils.getLaunchProviderRecursive(v);
+        if (v == null || !(v.getTag() instanceof ItemInfo) || firstParent == null) {
+            return false;
+        }
+        final ItemInfo itemInfo = (ItemInfo) v.getTag();
+        firstParent.fillInLogContainerData(itemInfo, child, targets);
+        return true;
+    }
+
+    protected void onFillInLogContainerData(@NonNull ItemInfo itemInfo, @NonNull Target target,
+            @NonNull ArrayList<Target> targets) {
+    }
+
+    private boolean mSessionStarted;
+    private long mElapsedContainerMillis;
+    private long mElapsedSessionMillis;
+    private long mActionDurationMillis;
+    private String mUuidStr;
+    protected InstantAppResolver mInstantAppResolver;
+    private boolean mAppOrTaskLaunch;
+    private boolean mPreviousHomeGesture;
+
+    private void fillComponentInfo(Target target, ComponentName cn) {
+        if (cn != null) {
+            target.packageNameHash = (mUuidStr + cn.getPackageName()).hashCode();
+            target.componentHash = (mUuidStr + cn.flattenToString()).hashCode();
+        }
+    }
+
+    public void logActionCommand(int command, int srcContainerType, int dstContainerType) {
+        logActionCommand(command, newContainerTarget(srcContainerType),
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
+    }
+
+    public void logActionCommand(int command, int srcContainerType, int dstContainerType,
+            int pageIndex) {
+        Target srcTarget = newContainerTarget(srcContainerType);
+        srcTarget.pageIndex = pageIndex;
+        logActionCommand(command, srcTarget,
+                dstContainerType >= 0 ? newContainerTarget(dstContainerType) : null);
+    }
+
+    public void logActionCommand(int command, Target srcTarget, Target dstTarget) {
+        LauncherEvent event = newLauncherEvent(newCommandAction(command), srcTarget);
+        if (command == Action.Command.STOP) {
+            if (mAppOrTaskLaunch || !mSessionStarted) {
+                mSessionStarted = false;
+                return;
+            }
+        }
+
+        if (dstTarget != null) {
+            event.destTarget = new Target[1];
+            event.destTarget[0] = dstTarget;
+            event.action.isStateChange = true;
+        }
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionOnControl(int action, int controlType) {
+        logActionOnControl(action, controlType, null);
+    }
+
+    public void logActionOnControl(int action, int controlType, int parentContainerType) {
+        logActionOnControl(action, controlType, null, parentContainerType);
+    }
+
+    /**
+     * Logs control action with proper parent hierarchy
+     */
+    public void logActionOnControl(int actionType, int controlType,
+            @Nullable View controlInContainer, int... parentTypes) {
+        Target control = newTarget(Target.Type.CONTROL);
+        control.controlType = controlType;
+        Action action = newAction(actionType);
+
+        ArrayList<Target> targets = makeTargetsList(control);
+        if (controlInContainer != null) {
+            fillLogContainer(controlInContainer, control, targets);
+        }
+        for (int parentContainerType : parentTypes) {
+            if (parentContainerType < 0) continue;
+            targets.add(newContainerTarget(parentContainerType));
+        }
+        LauncherEvent event = newLauncherEvent(action, targets);
+        if (actionType == Action.Touch.DRAGDROP) {
+            event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
+        }
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionTapOutside(Target target) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Type.TOUCH),
+                target);
+        event.action.isOutside = true;
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionBounceTip(int containerType) {
+        LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
+                newContainerTarget(containerType));
+        event.srcTarget[0].tipType = TipType.BOUNCE;
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionOnContainer(int action, int dir, int containerType) {
+        logActionOnContainer(action, dir, containerType, 0);
+    }
+
+    public void logActionOnContainer(int action, int dir, int containerType, int pageIndex) {
+        LauncherEvent event = newLauncherEvent(newTouchAction(action),
+                newContainerTarget(containerType));
+        event.action.dir = dir;
+        event.srcTarget[0].pageIndex = pageIndex;
+        dispatchUserEvent(event, null);
+    }
+
+    /**
+     * Used primarily for swipe up and down when state changes when swipe up happens from the
+     * navbar bezel, the {@param srcChildContainerType} is NAVBAR and
+     * {@param srcParentContainerType} is either one of the two
+     * (1) WORKSPACE: if the launcher is the foreground activity
+     * (2) APP: if another app was the foreground activity
+     */
+    public void logStateChangeAction(int action, int dir, int downX, int downY,
+            int srcChildTargetType, int srcParentContainerType, int dstContainerType,
+            int pageIndex) {
+        LauncherEvent event;
+        if (srcChildTargetType == ItemType.TASK) {
+            event = newLauncherEvent(newTouchAction(action),
+                    newItemTarget(srcChildTargetType),
+                    newContainerTarget(srcParentContainerType));
+        } else {
+            event = newLauncherEvent(newTouchAction(action),
+                    newContainerTarget(srcChildTargetType),
+                    newContainerTarget(srcParentContainerType));
+        }
+        event.destTarget = new Target[1];
+        event.destTarget[0] = newContainerTarget(dstContainerType);
+        event.action.dir = dir;
+        event.action.isStateChange = true;
+        event.srcTarget[0].pageIndex = pageIndex;
+        event.srcTarget[0].spanX = downX;
+        event.srcTarget[0].spanY = downY;
+        dispatchUserEvent(event, null);
+    }
+
+    public void logActionOnItem(int action, int dir, int itemType) {
+        logActionOnItem(action, dir, itemType, null, null);
+    }
+
+    /**
+     * Creates new {@link LauncherEvent} of ITEM target type with input arguments and dispatches it.
+     *
+     * @param touchAction ENUM value of {@link LauncherLogProto.Action.Touch} Action
+     * @param dir         ENUM value of {@link LauncherLogProto.Action.Direction} Action
+     * @param itemType    ENUM value of {@link LauncherLogProto.ItemType}
+     * @param gridX       Nullable X coordinate of item's position on the workspace grid
+     * @param gridY       Nullable Y coordinate of item's position on the workspace grid
+     */
+    public void logActionOnItem(int touchAction, int dir, int itemType,
+            @Nullable Integer gridX, @Nullable Integer gridY) {
+        Target itemTarget = newTarget(Target.Type.ITEM);
+        itemTarget.itemType = itemType;
+        ofNullable(gridX).ifPresent(value -> itemTarget.gridX = value);
+        ofNullable(gridY).ifPresent(value -> itemTarget.gridY = value);
+        LauncherEvent event = newLauncherEvent(newTouchAction(touchAction), itemTarget);
+        event.action.dir = dir;
+        dispatchUserEvent(event, null);
+    }
+
+    /**
+     * Logs proto lite version of LauncherEvent object to clearcut.
+     */
+    public void logLauncherEvent(
+            com.android.launcher3.userevent.LauncherLogProto.LauncherEvent launcherEvent) {
+
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
+        mAppOrTaskLaunch = false;
+        launcherEvent.toBuilder()
+                .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
+                .setElapsedSessionMillis(
+                        SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
+        try {
+            dispatchUserEvent(LauncherEvent.parseFrom(launcherEvent.toByteArray()), null);
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new RuntimeException("Cannot convert LauncherEvent from Lite to Nano version.");
+        }
+    }
+
+    public void logDeepShortcutsOpen(View icon) {
+        ItemInfo info = (ItemInfo) icon.getTag();
+        Target child = newItemTarget(info, mInstantAppResolver);
+        ArrayList<Target> targets = makeTargetsList(child);
+        fillLogContainer(icon, child, targets);
+        dispatchUserEvent(newLauncherEvent(newTouchAction(Action.Touch.TAP), targets), null);
+    }
+
+    public void logDragNDrop(DropTarget.DragObject dragObj, View dropTargetAsView) {
+        Target srcChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
+        ArrayList<Target> srcTargets = makeTargetsList(srcChild);
+
+
+        Target destChild = newItemTarget(dragObj.originalDragInfo, mInstantAppResolver);
+        ArrayList<Target> destTargets = makeTargetsList(destChild);
+
+        //dragObj.dragSource.fillInLogContainerData(dragObj.originalDragInfo, srcChild, srcTargets);
+        if (dropTargetAsView instanceof LogContainerProvider) {
+            ((LogContainerProvider) dropTargetAsView).fillInLogContainerData(dragObj.dragInfo,
+                    destChild, destTargets);
+        }
+        else {
+            destTargets.add(newDropTarget(dropTargetAsView));
+        }
+        LauncherEvent event = newLauncherEvent(newTouchAction(Action.Touch.DRAGDROP), srcTargets);
+        Target[] destTargetsArray = new Target[destTargets.size()];
+        destTargets.toArray(destTargetsArray);
+        event.destTarget = destTargetsArray;
+
+        event.actionDurationMillis = SystemClock.uptimeMillis() - mActionDurationMillis;
+        dispatchUserEvent(event, null);
+    }
+
+    public final void startSession() {
+        mSessionStarted = true;
+        mElapsedSessionMillis = SystemClock.uptimeMillis();
+        mElapsedContainerMillis = SystemClock.uptimeMillis();
+    }
+
+    public final void setPreviousHomeGesture(boolean homeGesture) {
+        mPreviousHomeGesture = homeGesture;
+    }
+
+    public final boolean isPreviousHomeGesture() {
+        return mPreviousHomeGesture;
+    }
+
+    public final void resetActionDurationMillis() {
+        mActionDurationMillis = SystemClock.uptimeMillis();
+    }
+
+    public void dispatchUserEvent(LauncherEvent ev, Intent intent) {
+        if (mPreviousHomeGesture) {
+            mPreviousHomeGesture = false;
+        }
+        mAppOrTaskLaunch = false;
+        ev.elapsedContainerMillis = SystemClock.uptimeMillis() - mElapsedContainerMillis;
+        ev.elapsedSessionMillis = SystemClock.uptimeMillis() - mElapsedSessionMillis;
+        if (!IS_VERBOSE) {
+            return;
+        }
+        LauncherLogProto.LauncherEvent liteLauncherEvent;
+        try {
+            liteLauncherEvent =
+                    LauncherLogProto.LauncherEvent.parseFrom(MessageNano.toByteArray(ev));
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("Cannot parse LauncherEvent from Nano to Lite version");
+        }
+        Log.d(TAG, liteLauncherEvent.toString());
+    }
+
+    /**
+     * Constructs an ArrayList with targets
+     */
+    public static ArrayList<Target> makeTargetsList(Target... targets) {
+        ArrayList<Target> result = new ArrayList<>();
+        for (Target target : targets) {
+            result.add(target);
+        }
+        return result;
+    }
+}
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 06a2c92..41ccbd7 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -20,9 +20,15 @@
 
 import static androidx.core.util.Preconditions.checkNotNull;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.EMPTY_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.MANUAL_LABEL;
 import static com.android.launcher3.logger.LauncherAtom.Attribute.SUGGESTED_LABEL;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_CUSTOM;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_EMPTY;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+import static com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState.FROM_SUGGESTED;
 
 import android.os.Process;
 
@@ -37,6 +43,10 @@
 import com.android.launcher3.logger.LauncherAtom.FromState;
 import com.android.launcher3.logger.LauncherAtom.ToState;
 import com.android.launcher3.model.ModelWriter;
+import com.android.launcher3.userevent.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto.Target;
+import com.android.launcher3.userevent.LauncherLogProto.Target.FromFolderLabelState;
+import com.android.launcher3.userevent.LauncherLogProto.Target.ToFolderLabelState;
 import com.android.launcher3.util.ContentWriter;
 
 import java.util.ArrayList;
@@ -349,4 +359,113 @@
         }
         return LauncherAtom.ToState.TO_STATE_UNSPECIFIED;
     }
+
+    /**
+     * Returns {@link LauncherLogProto.LauncherEvent} to log current folder label info.
+     *
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    public LauncherLogProto.LauncherEvent getFolderLabelStateLauncherEvent(FromState fromState,
+            ToState toState) {
+        return LauncherLogProto.LauncherEvent.newBuilder()
+                .setAction(LauncherLogProto.Action
+                        .newBuilder()
+                        .setType(LauncherLogProto.Action.Type.SOFT_KEYBOARD))
+                .addSrcTarget(Target
+                        .newBuilder()
+                        .setType(Target.Type.ITEM)
+                        .setItemType(LauncherLogProto.ItemType.EDITTEXT)
+                        .setFromFolderLabelState(convertFolderLabelState(fromState))
+                        .setToFolderLabelState(convertFolderLabelState(toState)))
+                .addSrcTarget(Target.newBuilder()
+                        .setType(Target.Type.CONTAINER)
+                        .setContainerType(LauncherLogProto.ContainerType.FOLDER)
+                        .setPageIndex(screenId)
+                        .setGridX(cellX)
+                        .setGridY(cellY)
+                        .setCardinality(contents.size()))
+                .addSrcTarget(newParentContainerTarget())
+                .build();
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private Target.Builder newParentContainerTarget() {
+        Target.Builder builder = Target.newBuilder().setType(Target.Type.CONTAINER);
+        switch (container) {
+            case CONTAINER_HOTSEAT:
+                return builder.setContainerType(LauncherLogProto.ContainerType.HOTSEAT);
+            case CONTAINER_DESKTOP:
+                return builder.setContainerType(LauncherLogProto.ContainerType.WORKSPACE);
+            default:
+                throw new AssertionError(String
+                        .format("Expected container to be either %s or %s but found %s.",
+                                CONTAINER_HOTSEAT,
+                                CONTAINER_DESKTOP,
+                                container));
+        }
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private static FromFolderLabelState convertFolderLabelState(FromState fromState) {
+        switch (fromState) {
+            case FROM_EMPTY:
+                return FROM_EMPTY;
+            case FROM_SUGGESTED:
+                return FROM_SUGGESTED;
+            case FROM_CUSTOM:
+                return FROM_CUSTOM;
+            default:
+                return FROM_FOLDER_LABEL_STATE_UNSPECIFIED;
+        }
+    }
+
+    /**
+     * @deprecated This method is used only for validation purpose and soon will be removed.
+     */
+    @Deprecated
+    private static ToFolderLabelState convertFolderLabelState(ToState toState) {
+        switch (toState) {
+            case UNCHANGED:
+                return ToFolderLabelState.UNCHANGED;
+            case TO_SUGGESTION0:
+                return ToFolderLabelState.TO_SUGGESTION0_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION1_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION1_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION1_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION1_WITH_EMPTY_PRIMARY;
+            case TO_SUGGESTION2_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION2_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION2_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION2_WITH_EMPTY_PRIMARY;
+            case TO_SUGGESTION3_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION3_WITH_VALID_PRIMARY;
+            case TO_SUGGESTION3_WITH_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_SUGGESTION3_WITH_EMPTY_PRIMARY;
+            case TO_EMPTY_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_EMPTY_WITH_VALID_PRIMARY;
+            case TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_EMPTY_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+            case TO_EMPTY_WITH_EMPTY_SUGGESTIONS:
+                return ToFolderLabelState.TO_EMPTY_WITH_EMPTY_SUGGESTIONS;
+            case TO_EMPTY_WITH_SUGGESTIONS_DISABLED:
+                return ToFolderLabelState.TO_EMPTY_WITH_SUGGESTIONS_DISABLED;
+            case TO_CUSTOM_WITH_VALID_PRIMARY:
+                return ToFolderLabelState.TO_CUSTOM_WITH_VALID_PRIMARY;
+            case TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY:
+                return ToFolderLabelState.TO_CUSTOM_WITH_VALID_SUGGESTIONS_AND_EMPTY_PRIMARY;
+            case TO_CUSTOM_WITH_EMPTY_SUGGESTIONS:
+                return ToFolderLabelState.TO_CUSTOM_WITH_EMPTY_SUGGESTIONS;
+            case TO_CUSTOM_WITH_SUGGESTIONS_DISABLED:
+                return ToFolderLabelState.TO_CUSTOM_WITH_SUGGESTIONS_DISABLED;
+            default:
+                return ToFolderLabelState.TO_FOLDER_LABEL_STATE_UNSPECIFIED;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/model/data/RemoteActionItemInfo.java b/src/com/android/launcher3/model/data/RemoteActionItemInfo.java
deleted file mode 100644
index 81f7f3a..0000000
--- a/src/com/android/launcher3/model/data/RemoteActionItemInfo.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.model.data;
-
-import android.app.RemoteAction;
-import android.os.Process;
-
-/**
- * Represents a launchable {@link RemoteAction}
- */
-public class RemoteActionItemInfo extends ItemInfoWithIcon {
-
-    private final RemoteAction mRemoteAction;
-    private final String mToken;
-    private final boolean mShouldStart;
-
-    public RemoteActionItemInfo(RemoteAction remoteAction, String token, boolean shouldStart) {
-        mShouldStart = shouldStart;
-        mToken = token;
-        mRemoteAction = remoteAction;
-        title = remoteAction.getTitle();
-        user = Process.myUserHandle();
-    }
-
-    public RemoteActionItemInfo(RemoteActionItemInfo info) {
-        super(info);
-        this.mShouldStart = info.mShouldStart;
-        this.mRemoteAction = info.mRemoteAction;
-        this.mToken = info.mToken;
-    }
-
-    @Override
-    public ItemInfoWithIcon clone() {
-        return new RemoteActionItemInfo(this);
-    }
-
-    public RemoteAction getRemoteAction() {
-        return mRemoteAction;
-    }
-
-    public String getToken() {
-        return mToken;
-    }
-
-    /**
-     * Getter method for mShouldStart
-     */
-    public boolean shouldStartInLauncher() {
-        return mShouldStart;
-    }
-}
diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java
index 9b06523..32f060b 100644
--- a/src/com/android/launcher3/notification/NotificationMainView.java
+++ b/src/com/android/launcher3/notification/NotificationMainView.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.notification;
 
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DISMISSED;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -42,6 +41,7 @@
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.util.Themes;
 
 /**
@@ -168,7 +168,10 @@
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.getPopupDataProvider().cancelNotification(
                 mNotificationInfo.notificationKey);
-        launcher.getStatsLogManager().logger().log(LAUNCHER_NOTIFICATION_DISMISSED);
+        launcher.getUserEventDispatcher().logActionOnItem(
+                LauncherLogProto.Action.Touch.SWIPE,
+                LauncherLogProto.Action.Direction.RIGHT, // Assume all swipes are right for logging.
+                LauncherLogProto.ItemType.NOTIFICATION);
     }
 
     // SingleAxisSwipeDetector.Listener's
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index fa25114..753a6dd 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.pm;
 
 import static com.android.launcher3.Utilities.getPrefs;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -32,7 +31,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.SessionCommitReceiver;
@@ -41,10 +39,10 @@
 import com.android.launcher3.model.ItemInstallQueue;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
-import com.android.launcher3.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -67,27 +65,27 @@
 
     private final LauncherApps mLauncherApps;
     private final Context mAppContext;
+    private final IntSet mPromiseIconIds;
 
     private final PackageInstaller mInstaller;
     private final HashMap<String, Boolean> mSessionVerifiedMap = new HashMap<>();
 
-    private IntSet mPromiseIconIds;
-
     public InstallSessionHelper(Context context) {
         mInstaller = context.getPackageManager().getPackageInstaller();
         mAppContext = context.getApplicationContext();
         mLauncherApps = context.getSystemService(LauncherApps.class);
+
+        mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
+                getPrefs(context).getString(PROMISE_ICON_IDS, "")));
+
+        cleanUpPromiseIconIds();
     }
 
-    @WorkerThread
-    private IntSet getPromiseIconIds() {
-        Preconditions.assertWorkerThread();
-        if (mPromiseIconIds != null) {
-            return mPromiseIconIds;
-        }
-        mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
-                getPrefs(mAppContext).getString(PROMISE_ICON_IDS, "")));
+    public static UserHandle getUserHandle(SessionInfo info) {
+        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
+    }
 
+    protected void cleanUpPromiseIconIds() {
         IntArray existingIds = new IntArray();
         for (SessionInfo info : getActiveSessions().values()) {
             existingIds.add(info.getSessionId());
@@ -102,7 +100,6 @@
         for (int i = idsToRemove.size() - 1; i >= 0; --i) {
             mPromiseIconIds.getArray().removeValue(idsToRemove.get(i));
         }
-        return mPromiseIconIds;
     }
 
     public HashMap<PackageUserKey, SessionInfo> getActiveSessions() {
@@ -129,7 +126,7 @@
 
     private void updatePromiseIconPrefs() {
         getPrefs(mAppContext).edit()
-                .putString(PROMISE_ICON_IDS, getPromiseIconIds().getArray().toConcatString())
+                .putString(PROMISE_ICON_IDS, mPromiseIconIds.getArray().toConcatString())
                 .apply();
     }
 
@@ -187,15 +184,13 @@
         return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
     }
 
-    @WorkerThread
     public boolean promiseIconAddedForId(int sessionId) {
-        return getPromiseIconIds().contains(sessionId);
+        return mPromiseIconIds.contains(sessionId);
     }
 
-    @WorkerThread
     public void removePromiseIconId(int sessionId) {
-        if (promiseIconAddedForId(sessionId)) {
-            getPromiseIconIds().getArray().removeValue(sessionId);
+        if (mPromiseIconIds.contains(sessionId)) {
+            mPromiseIconIds.getArray().removeValue(sessionId);
             updatePromiseIconPrefs();
         }
     }
@@ -208,7 +203,6 @@
      * - The app is not already installed
      * - A promise icon for the session has not already been created
      */
-    @WorkerThread
     void tryQueuePromiseAppIcon(PackageInstaller.SessionInfo sessionInfo) {
         if (FeatureFlags.PROMISE_APPS_NEW_INSTALLS.get()
                 && SessionCommitReceiver.isEnabled(mAppContext)
@@ -216,24 +210,25 @@
                 && sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
                 && sessionInfo.getAppIcon() != null
                 && !TextUtils.isEmpty(sessionInfo.getAppLabel())
-                && !promiseIconAddedForId(sessionInfo.getSessionId())
+                && !mPromiseIconIds.contains(sessionInfo.getSessionId())
                 && new PackageManagerHelper(mAppContext).getApplicationInfo(
                         sessionInfo.getAppPackageName(), getUserHandle(sessionInfo), 0) == null) {
             ItemInstallQueue.INSTANCE.get(mAppContext)
                     .queueItem(sessionInfo.getAppPackageName(), getUserHandle(sessionInfo));
 
-            getPromiseIconIds().add(sessionInfo.getSessionId());
+            mPromiseIconIds.add(sessionInfo.getSessionId());
             updatePromiseIconPrefs();
         }
     }
 
-    public InstallSessionTracker registerInstallTracker(InstallSessionTracker.Callback callback) {
+    public InstallSessionTracker registerInstallTracker(
+            InstallSessionTracker.Callback callback, LooperExecutor executor) {
         InstallSessionTracker tracker = new InstallSessionTracker(this, callback);
 
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
-            mInstaller.registerSessionCallback(tracker, MODEL_EXECUTOR.getHandler());
+            mInstaller.registerSessionCallback(tracker, executor.getHandler());
         } else {
-            mLauncherApps.registerPackageInstallerSessionCallback(MODEL_EXECUTOR, tracker);
+            mLauncherApps.registerPackageInstallerSessionCallback(executor, tracker);
         }
         return tracker;
     }
@@ -245,8 +240,4 @@
             mLauncherApps.unregisterPackageInstallerSessionCallback(tracker);
         }
     }
-
-    public static UserHandle getUserHandle(SessionInfo info) {
-        return Utilities.ATLEAST_Q ? info.getUser() : Process.myUserHandle();
-    }
 }
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b0b907a..eb3ca73 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -24,11 +24,8 @@
 import android.os.UserHandle;
 import android.util.SparseArray;
 
-import androidx.annotation.WorkerThread;
-
 import com.android.launcher3.util.PackageUserKey;
 
-@WorkerThread
 public class InstallSessionTracker extends PackageInstaller.SessionCallback {
 
     // Lazily initialized
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index 5ade22b..2d7d6b0 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -60,9 +60,6 @@
     private void onUsersChanged(Intent intent) {
         enableAndResetCache();
         mUserChangeListeners.forEach(Runnable::run);
-        if (TestProtocol.sDebugTracing) {
-            Log.d(TestProtocol.WORK_PROFILE_REMOVED, "profile changed", new Exception());
-        }
     }
 
     /**
@@ -107,6 +104,9 @@
                 mUsers = null;
                 mUserToSerialMap = null;
             }
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.WORK_PROFILE_REMOVED, "Work profile removed", new Exception());
+            }
         }
     }
 
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 90285c4..d5b32fc 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -151,52 +151,32 @@
      * @param viewsToFlip number of views from the top to to flip in case of reverse order
      */
     protected void reorderAndShow(int viewsToFlip) {
-        setupForDisplay();
-        boolean reverseOrder = mIsAboveIcon;
-        if (reverseOrder) {
-            reverseOrder(viewsToFlip);
-        }
-        onInflationComplete(reverseOrder);
-        addArrow();
-        animateOpen();
-    }
-
-    /**
-     * Shows the popup at the desired location.
-     */
-    protected void show() {
-        setupForDisplay();
-        onInflationComplete(false);
-        addArrow();
-        animateOpen();
-    }
-
-    private void setupForDisplay() {
         setVisibility(View.INVISIBLE);
         mIsOpen = true;
         getPopupContainer().addView(this);
         orientAboutObject();
-    }
 
-    private void reverseOrder(int viewsToFlip) {
-        int count = getChildCount();
-        ArrayList<View> allViews = new ArrayList<>(count);
-        for (int i = 0; i < count; i++) {
-            if (i == viewsToFlip) {
-                Collections.reverse(allViews);
+        boolean reverseOrder = mIsAboveIcon;
+        if (reverseOrder) {
+            int count = getChildCount();
+            ArrayList<View> allViews = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                if (i == viewsToFlip) {
+                    Collections.reverse(allViews);
+                }
+                allViews.add(getChildAt(i));
             }
-            allViews.add(getChildAt(i));
-        }
-        Collections.reverse(allViews);
-        removeAllViews();
-        for (int i = 0; i < count; i++) {
-            addView(allViews.get(i));
-        }
+            Collections.reverse(allViews);
+            removeAllViews();
+            for (int i = 0; i < count; i++) {
+                addView(allViews.get(i));
+            }
 
-        orientAboutObject();
-    }
+            orientAboutObject();
+        }
+        onInflationComplete(reverseOrder);
 
-    private void addArrow() {
+        // Add the arrow.
         final Resources res = getResources();
         final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart()
                 ? R.dimen.popup_arrow_horizontal_center_start
@@ -234,6 +214,8 @@
 
         mArrow.setPivotX(arrowLp.width / 2);
         mArrow.setPivotY(mIsAboveIcon ? arrowLp.height : 0);
+
+        animateOpen();
     }
 
     protected boolean isAlignedWithStart() {
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 6d92b8b..26b32b8 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -19,8 +19,10 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
 import static com.android.launcher3.Utilities.squaredHypot;
 import static com.android.launcher3.Utilities.squaredTouchSlop;
+import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.AnimatorSet;
@@ -158,7 +160,8 @@
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             BaseDragLayer dl = getPopupContainer();
             if (!dl.isEventOverView(this, ev)) {
-                // TODO: add WW log if want to log if tap closed deep shortcut container.
+                mLauncher.getUserEventDispatcher().logActionTapOutside(
+                        newContainerTarget(ContainerType.DEEPSHORTCUTS));
                 close(true);
 
                 // We let touches on the original icon go through so that users can launch
@@ -432,9 +435,7 @@
                     // Make sure we keep the original icon hidden while it is being dragged.
                     mOriginalIcon.setVisibility(INVISIBLE);
                 } else {
-                    // TODO: add WW logging if want to add logging for long press on popup
-                    //  container.
-                    //  mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
+                    mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
                     if (!mIsAboveIcon) {
                         // Show the icon but keep the text hidden.
                         mOriginalIcon.setVisibility(VISIBLE);
diff --git a/src/com/android/launcher3/popup/RemoteActionShortcut.java b/src/com/android/launcher3/popup/RemoteActionShortcut.java
index 7c393ad..61829c0 100644
--- a/src/com/android/launcher3/popup/RemoteActionShortcut.java
+++ b/src/com/android/launcher3/popup/RemoteActionShortcut.java
@@ -37,6 +37,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
 
 @TargetApi(Build.VERSION_CODES.Q)
 public class RemoteActionShortcut extends SystemShortcut<BaseDraggingActivity> {
@@ -106,6 +107,9 @@
                     Toast.LENGTH_SHORT)
                     .show();
         }
+
+        mTarget.getUserEventDispatcher().logActionOnControl(LauncherLogProto.Action.Touch.TAP,
+                LauncherLogProto.ControlType.REMOTE_ACTION_SHORTCUT, view);
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 577fe4a..81302ac 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -21,6 +21,8 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PackageUserKey;
@@ -115,6 +117,8 @@
                     (WidgetsBottomSheet) mTarget.getLayoutInflater().inflate(
                             R.layout.widgets_bottom_sheet, mTarget.getDragLayer(), false);
             widgetsBottomSheet.populateAndShow(mItemInfo);
+            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.WIDGETS_BUTTON, view);
             mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_WIDGETS_TAP);
         }
@@ -135,6 +139,8 @@
             Rect sourceBounds = mTarget.getViewBounds(view);
             new PackageManagerHelper(mTarget).startDetailsActivityForInfo(
                     mItemInfo, sourceBounds, ActivityOptions.makeBasic().toBundle());
+            mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
+                    ControlType.APPINFO_TARGET, view);
             mTarget.getStatsLogManager().logger().withItemInfo(mItemInfo)
                     .log(LAUNCHER_SYSTEM_SHORTCUT_APP_INFO_TAP);
         }
diff --git a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
index f4b059d..4baecb7 100644
--- a/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
+++ b/src/com/android/launcher3/settings/DeveloperOptionsFragment.java
@@ -306,15 +306,6 @@
             return true;
         });
         sandboxCategory.addPreference(launchAssistantTutorialPreference);
-        Preference launchSandboxModeTutorialPreference = new Preference(context);
-        launchSandboxModeTutorialPreference.setKey("launchSandboxMode");
-        launchSandboxModeTutorialPreference.setTitle("Launch Sandbox Mode");
-        launchSandboxModeTutorialPreference.setSummary("Practice navigation gestures");
-        launchSandboxModeTutorialPreference.setOnPreferenceClickListener(preference -> {
-            startActivity(launchSandboxIntent.putExtra("tutorial_type", "SANDBOX_MODE"));
-            return true;
-        });
-        sandboxCategory.addPreference(launchSandboxModeTutorialPreference);
     }
 
     private String toName(String action) {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index fd1d965..b8a184f 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -15,12 +15,11 @@
  */
 package com.android.launcher3.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
-
 import android.content.Context;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
@@ -31,7 +30,7 @@
             | FLAG_HAS_SYS_UI_SCRIM;
 
     public HintState(int id) {
-        super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
+        super(id, ContainerType.DEFAULT_CONTAINERTYPE, STATE_FLAGS);
     }
 
     @Override
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 45172b5..2a4f887 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
-
 import android.content.Context;
 import android.graphics.Rect;
 
@@ -24,6 +22,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Definition for spring loaded state used during drag and drop.
@@ -36,7 +35,7 @@
             | FLAG_HIDE_BACK_BUTTON;
 
     public SpringLoadedState(int id) {
-        super(id, LAUNCHER_STATE_HOME, STATE_FLAGS);
+        super(id, ContainerType.WORKSPACE, STATE_FLAGS);
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 9fd53e2..8ee5a6e 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -21,9 +21,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEDOWN;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_UNKNOWN_SWIPEUP;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
@@ -55,6 +52,9 @@
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.TouchController;
 
@@ -190,6 +190,11 @@
 
     protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
 
+    /**
+     * Returns the container that the touch started from when leaving NORMAL state.
+     */
+    protected abstract int getLogContainerTypeForNormalState(MotionEvent ev);
+
     private boolean reinitCurrentAnimation(boolean reachedToState, boolean isDragTowardPositive) {
         LauncherState newFromState = mFromState == null ? mLauncher.getStateManager().getState()
                 : reachedToState ? mToState : mFromState;
@@ -302,11 +307,11 @@
     public boolean onDrag(float displacement, MotionEvent ev) {
         if (!mIsLogContainerSet) {
             if (mStartState == ALL_APPS) {
-                mStartContainerType = LAUNCHER_STATE_ALLAPPS;
+                mStartContainerType = ContainerType.ALLAPPS;
             } else if (mStartState == NORMAL) {
-                mStartContainerType = LAUNCHER_STATE_HOME;
+                mStartContainerType = getLogContainerTypeForNormalState(ev);
             } else if (mStartState == OVERVIEW) {
-                mStartContainerType = LAUNCHER_STATE_OVERVIEW;
+                mStartContainerType = ContainerType.TASKSWITCHER;
             }
             mIsLogContainerSet = true;
         }
@@ -396,6 +401,7 @@
     @Override
     public void onDragEnd(float velocity) {
         boolean fling = mDetector.isFling(velocity);
+        final int logAction = fling ? Touch.FLING : Touch.SWIPE;
 
         boolean blockedFling = fling && mFlingBlockCheck.isBlocked();
         if (blockedFling) {
@@ -452,7 +458,7 @@
             }
         }
 
-        mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState));
+        mCurrentAnimation.setEndAction(() -> onSwipeInteractionCompleted(targetState, logAction));
         ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
         anim.setFloatValues(startProgress, endProgress);
         maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
@@ -516,7 +522,11 @@
                 .setInterpolator(scrollInterpolatorForVelocity(velocity));
     }
 
-    protected void onSwipeInteractionCompleted(LauncherState targetState) {
+    protected int getDirectionForLog() {
+        return mToState.ordinal > mFromState.ordinal ? Direction.UP : Direction.DOWN;
+    }
+
+    protected void onSwipeInteractionCompleted(LauncherState targetState, int logAction) {
         if (mAtomicComponentsController != null) {
             mAtomicComponentsController.getAnimationPlayer().end();
             mAtomicComponentsController = null;
@@ -525,18 +535,18 @@
         boolean shouldGoToTargetState = true;
         if (mPendingAnimation != null) {
             boolean reachedTarget = mToState == targetState;
-            mPendingAnimation.finish(reachedTarget);
+            mPendingAnimation.finish(reachedTarget, logAction);
             mPendingAnimation = null;
             shouldGoToTargetState = !reachedTarget;
         }
         if (shouldGoToTargetState) {
-            goToTargetState(targetState);
+            goToTargetState(targetState, logAction);
         }
     }
 
-    protected void goToTargetState(LauncherState targetState) {
+    protected void goToTargetState(LauncherState targetState, int logAction) {
         if (targetState != mStartState) {
-            logReachedState(targetState);
+            logReachedState(logAction, targetState);
         }
         if (!mLauncher.isInState(targetState)) {
             // If we're already in the target state, don't jump to it at the end of the animation in
@@ -546,18 +556,24 @@
         mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
     }
 
-    private void logReachedState(LauncherState targetState) {
+    private void logReachedState(int logAction, LauncherState targetState) {
         // Transition complete. log the action
+        mLauncher.getUserEventDispatcher().logStateChangeAction(logAction,
+                getDirectionForLog(), mDetector.getDownX(), mDetector.getDownY(),
+                mStartContainerType /* e.g., hotseat */,
+                mStartState.containerType /* e.g., workspace */,
+                targetState.containerType,
+                mLauncher.getWorkspace().getCurrentPage());
         mLauncher.getStatsLogManager().logger()
-                .withSrcState(mStartState.statsLogOrdinal)
-                .withDstState(targetState.statsLogOrdinal)
+                .withSrcState(StatsLogManager.containerTypeToAtomState(mStartState.containerType))
+                .withDstState(StatsLogManager.containerTypeToAtomState(targetState.containerType))
                 .withContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
                         .setWorkspace(
                                 LauncherAtom.WorkspaceContainer.newBuilder()
                                         .setPageIndex(mLauncher.getWorkspace().getCurrentPage()))
                         .build())
-                .log(StatsLogManager.getLauncherAtomEvent(mStartState.statsLogOrdinal,
-                            targetState.statsLogOrdinal, mToState.ordinal > mFromState.ordinal
+                .log(StatsLogManager.getLauncherAtomEvent(mStartState.containerType,
+                            targetState.containerType, mToState.ordinal > mFromState.ordinal
                                     ? LAUNCHER_UNKNOWN_SWIPEUP
                                     : LAUNCHER_UNKNOWN_SWIPEDOWN));
     }
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index f9dcf2d..4a202b6 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * TouchController to switch between NORMAL and ALL_APPS state.
@@ -69,6 +70,12 @@
     }
 
     @Override
+    protected int getLogContainerTypeForNormalState(MotionEvent ev) {
+        return mLauncher.getDragLayer().isEventOverView(mLauncher.getHotseat(), mTouchDownEvent)
+                ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
+    }
+
+    @Override
     protected float initCurrentAnimation(@AnimationFlags int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index d56391d..61d6f7d 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -25,9 +25,7 @@
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
 
 import android.app.AlertDialog;
-import android.app.PendingIntent;
 import android.content.Intent;
-import android.content.IntentSender;
 import android.content.pm.LauncherApps;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.os.Process;
@@ -51,7 +49,6 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PromiseAppInfo;
-import com.android.launcher3.model.data.RemoteActionItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.testing.TestLogging;
@@ -239,27 +236,6 @@
         startAppShortcutOrInfoActivity(v, shortcut, launcher);
     }
 
-    /**
-     * Event handler for a {@link android.app.RemoteAction} click
-     *
-     */
-    public static void onClickRemoteAction(Launcher launcher,
-            RemoteActionItemInfo remoteActionInfo) {
-        try {
-            PendingIntent pendingIntent = remoteActionInfo.getRemoteAction().getActionIntent();
-            if (remoteActionInfo.shouldStartInLauncher()) {
-                launcher.startIntentSenderForResult(pendingIntent.getIntentSender(), 0, null, 0, 0,
-                        0);
-            } else {
-                pendingIntent.send();
-            }
-        } catch (PendingIntent.CanceledException | IntentSender.SendIntentException e) {
-            Toast.makeText(launcher,
-                    launcher.getResources().getText(R.string.shortcut_not_available),
-                    Toast.LENGTH_SHORT).show();
-        }
-    }
-
     private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
         TestLogging.recordEvent(
                 TestProtocol.SEQUENCE_MAIN, "start: startAppShortcutOrInfoActivity");
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index 17f02be..fb02f79 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -72,6 +72,7 @@
         out.halfPageSize = view.getNormalChildHeight() / 2;
         out.halfScreenSize = view.getMeasuredHeight() / 2;
         out.screenCenter = insets.top + view.getPaddingTop() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleY();
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 114b75a..354d78d 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -112,6 +112,7 @@
         public int halfPageSize;
         public int screenCenter;
         public int halfScreenSize;
+        public float pageParentScale;
     }
 
     class ChildBounds {
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 5f5b2d1..06479e6 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -70,6 +70,7 @@
         out.halfPageSize = view.getNormalChildWidth() / 2;
         out.halfScreenSize = view.getMeasuredWidth() / 2;
         out.screenCenter = insets.left + view.getPaddingLeft() + out.scroll + out.halfPageSize;
+        out.pageParentScale = view.getScaleX();
     }
 
     @Override
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index a85ae45..0a32734 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -20,10 +20,8 @@
 import android.os.Process;
 
 import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Various different executors used in Launcher
@@ -85,29 +83,4 @@
      */
     public static final LooperExecutor MODEL_EXECUTOR =
             new LooperExecutor(createAndStartNewLooper("launcher-loader"));
-
-    /**
-     * A simple ThreadFactory to set the thread name and priority when used with executors.
-     */
-    public static class SimpleThreadFactory implements ThreadFactory {
-
-        private final int mPriority;
-        private final String mNamePrefix;
-
-        private final AtomicInteger mCount = new AtomicInteger(0);
-
-        public SimpleThreadFactory(String namePrefix, int priority) {
-            mNamePrefix = namePrefix;
-            mPriority = priority;
-        }
-
-        @Override
-        public Thread newThread(Runnable runnable) {
-            Thread t = new Thread(() -> {
-                Process.setThreadPriority(mPriority);
-                runnable.run();
-            }, mNamePrefix + mCount.incrementAndGet());
-            return t;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index d9a14e9..52a82f8 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.views;
 
+import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA;
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
@@ -22,6 +23,9 @@
 import static com.android.launcher3.views.IconLabelDotView.setIconAndDotVisible;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.graphics.Canvas;
@@ -70,6 +74,7 @@
     private static @Nullable IconLoadResult sIconLoadResult;
 
     public static final float SHAPE_PROGRESS_DURATION = 0.10f;
+    private static final int FADE_DURATION_MS = 200;
     private static final RectF sTmpRectF = new RectF();
     private static final Object[] sTmpObjArray = new Object[1];
 
@@ -84,9 +89,6 @@
 
     private IconLoadResult mIconLoadResult;
 
-    // Draw the drawable of the BubbleTextView behind ClipIconView to reveal the built in shadow.
-    private View mBtvDrawable;
-
     private ClipIconView mClipIconView;
     private @Nullable Drawable mBadge;
 
@@ -96,6 +98,7 @@
 
     private final Rect mFinalDrawableBounds = new Rect();
 
+    private AnimatorSet mFadeAnimatorSet;
     private ListenerView mListenerView;
     private Runnable mFastFinishRunnable;
 
@@ -113,8 +116,6 @@
         mIsRtl = Utilities.isRtl(getResources());
         mListenerView = new ListenerView(context, attrs);
         mClipIconView = new ClipIconView(context, attrs);
-        mBtvDrawable = new ImageView(context, attrs);
-        addView(mBtvDrawable);
         addView(mClipIconView);
         setWillNotDraw(false);
     }
@@ -175,7 +176,6 @@
         setLayoutParams(lp);
 
         mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
-        mBtvDrawable.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height));
     }
 
     private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
@@ -292,8 +292,6 @@
         drawable = drawable == null ? null : drawable.getConstantState().newDrawable();
         int iconOffset = getOffsetForIconBounds(l, drawable, pos);
         synchronized (iconLoadResult) {
-            iconLoadResult.btvDrawable = btvIcon == null || drawable == btvIcon
-                    ? null : btvIcon.getConstantState().newDrawable();
             iconLoadResult.drawable = drawable;
             iconLoadResult.badge = badge;
             iconLoadResult.iconOffset = iconOffset;
@@ -313,8 +311,7 @@
      * @param iconOffset The amount of offset needed to match this view with the original view.
      */
     @UiThread
-    private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge,
-            @Nullable Drawable btvIcon, int iconOffset) {
+    private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) {
         final InsettableFrameLayout.LayoutParams lp =
                 (InsettableFrameLayout.LayoutParams) getLayoutParams();
         mBadge = badge;
@@ -345,10 +342,6 @@
                 mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight);
             }
         }
-
-        if (!mIsOpening && btvIcon != null) {
-            mBtvDrawable.setBackground(btvIcon);
-        }
         invalidate();
     }
 
@@ -367,7 +360,7 @@
         synchronized (mIconLoadResult) {
             if (mIconLoadResult.isIconLoaded) {
                 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
-                        mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
+                        mIconLoadResult.iconOffset);
                 setIconAndDotVisible(originalView, false);
             } else {
                 mIconLoadResult.onIconLoaded = () -> {
@@ -376,7 +369,7 @@
                     }
 
                     setIcon(mIconLoadResult.drawable, mIconLoadResult.badge,
-                            mIconLoadResult.btvDrawable, mIconLoadResult.iconOffset);
+                            mIconLoadResult.iconOffset);
 
                     setVisibility(VISIBLE);
                     setIconAndDotVisible(originalView, false);
@@ -441,6 +434,10 @@
             mEndRunnable.run();
             mEndRunnable = null;
         }
+        if (mFadeAnimatorSet != null) {
+            mFadeAnimatorSet.end();
+            mFadeAnimatorSet = null;
+        }
     }
 
     @Override
@@ -549,16 +546,8 @@
                     setIconAndDotVisible(originalView, true);
                     view.finish(dragLayer);
                 } else {
-                    originalView.setVisibility(VISIBLE);
-                    if (originalView instanceof IconLabelDotView) {
-                        setIconAndDotVisible(originalView, true);
-                    }
-                    if (originalView instanceof BubbleTextView) {
-                        BubbleTextView btv = (BubbleTextView) originalView;
-                        btv.setIconVisible(true);
-                        btv.setForceHideDot(true);
-                    }
-                    view.finish(dragLayer);
+                    view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
+                    view.mFadeAnimatorSet.start();
                 }
             } else {
                 view.finish(dragLayer);
@@ -575,6 +564,47 @@
         return view;
     }
 
+    private AnimatorSet createFadeAnimation(View originalView, DragLayer dragLayer) {
+        AnimatorSet fade = new AnimatorSet();
+        fade.setDuration(FADE_DURATION_MS);
+        fade.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                originalView.setVisibility(VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finish(dragLayer);
+            }
+        });
+
+        if (originalView instanceof IconLabelDotView) {
+            fade.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    setIconAndDotVisible(originalView, true);
+                }
+            });
+        }
+
+        if (originalView instanceof BubbleTextView) {
+            BubbleTextView btv = (BubbleTextView) originalView;
+            fade.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    btv.setIconVisible(true);
+                    btv.setForceHideDot(true);
+                }
+            });
+            fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255));
+        } else if (!(originalView instanceof FolderIcon)) {
+            fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+        }
+
+        return fade;
+    }
+
     private void finish(DragLayer dragLayer) {
         ((ViewGroup) dragLayer.getParent()).removeView(this);
         dragLayer.removeView(mListenerView);
@@ -598,7 +628,11 @@
         mLoadIconSignal = null;
         mEndRunnable = null;
         mFinalDrawableBounds.setEmpty();
+        if (mFadeAnimatorSet != null) {
+            mFadeAnimatorSet.cancel();
+        }
         mPositionOut = null;
+        mFadeAnimatorSet = null;
         mListenerView.setListener(null);
         mOriginalIcon = null;
         mOnTargetChangeRunnable = null;
@@ -606,13 +640,11 @@
         sTmpObjArray[0] = null;
         mIconLoadResult = null;
         mClipIconView.recycle();
-        mBtvDrawable.setBackground(null);
         mFastFinishRunnable = null;
     }
 
     private static class IconLoadResult {
         final ItemInfo itemInfo;
-        Drawable btvDrawable;
         Drawable drawable;
         Drawable badge;
         int iconOffset;
diff --git a/src/com/android/launcher3/views/HeroSearchResultView.java b/src/com/android/launcher3/views/HeroSearchResultView.java
index dd322d9..a8e1c6b 100644
--- a/src/com/android/launcher3/views/HeroSearchResultView.java
+++ b/src/com/android/launcher3/views/HeroSearchResultView.java
@@ -16,16 +16,12 @@
 package com.android.launcher3.views;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.content.Context;
 import android.content.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;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
@@ -35,10 +31,9 @@
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController.PayloadResultHandler;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.graphics.DragPreviewProvider;
@@ -58,10 +53,9 @@
  * A view representing a high confidence app search result that includes shortcuts
  */
 public class HeroSearchResultView extends LinearLayout implements DragSource,
-        PayloadResultHandler<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> {
+        AllAppsSearchBarController.PayloadResultHandler<List<ItemInfoWithIcon>> {
 
     public static final int MAX_SHORTCUTS_COUNT = 2;
-    private final Object[] mTargetInfo = createTargetInfo();
     BubbleTextView mBubbleTextView;
     View mIconView;
     BubbleTextView[] mDeepShortcutTextViews = new BubbleTextView[2];
@@ -108,7 +102,7 @@
                             grid.allAppsIconSizePx));
             bubbleTextView.setOnClickListener(view -> {
                 WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) bubbleTextView.getTag();
-                SearchTargetEvent event = getSearchTargetEvent(
+                SearchTargetEvent event = new SearchTargetEvent(
                         SearchTarget.ItemType.APP_HERO,
                         SearchTargetEvent.CHILD_SELECT);
                 event.bundle = getAppBundle(itemInfo);
@@ -125,25 +119,15 @@
      * Apply {@link ItemInfo} for appIcon and shortcut Icons
      */
     @Override
-    public void applyAdapterInfo(
-            AdapterItemWithPayload<List<Pair<ShortcutInfo, ItemInfoWithIcon>>> adapterItem) {
+    public void applyAdapterInfo(AdapterItemWithPayload<List<ItemInfoWithIcon>> adapterItem) {
         mBubbleTextView.applyFromApplicationInfo(adapterItem.appInfo);
         mIconView.setBackground(mBubbleTextView.getIcon());
         mIconView.setTag(adapterItem.appInfo);
-        List<Pair<ShortcutInfo, ItemInfoWithIcon>> shortcutDetails = adapterItem.getPayload();
-        LauncherAppState appState = LauncherAppState.getInstance(getContext());
+        List<ItemInfoWithIcon> shorcutInfos = adapterItem.getPayload();
         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);
-                //apply ItemInfo and prepare view
-                shortcutView.applyFromWorkspaceItem((WorkspaceItemInfo) p.second);
-                MODEL_EXECUTOR.execute(() -> {
-                    // load unbadged shortcut in background and update view when icon ready
-                    appState.getIconCache().getUnbadgedShortcutIcon(p.second, p.first);
-                    MAIN_EXECUTOR.post(() -> shortcutView.reapplyItemInfo(p.second));
-                });
+            mDeepShortcutTextViews[i].setVisibility(shorcutInfos.size() > i ? VISIBLE : GONE);
+            if (i < shorcutInfos.size()) {
+                mDeepShortcutTextViews[i].applyFromItemInfoWithIcon(shorcutInfos.get(i));
             }
         }
         mPlugin = adapterItem.getPlugin();
@@ -151,11 +135,6 @@
     }
 
     @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
-    @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
         mBubbleTextView.setVisibility(VISIBLE);
         mBubbleTextView.setIconVisible(true);
@@ -190,7 +169,7 @@
             mLauncher.getWorkspace().beginDragShared(mContainer.mBubbleTextView,
                     draggableView, mContainer, itemInfo, previewProvider, new DragOptions());
 
-            SearchTargetEvent event = mContainer.getSearchTargetEvent(
+            SearchTargetEvent event = new SearchTargetEvent(
                     SearchTarget.ItemType.APP_HERO, SearchTargetEvent.LONG_PRESS);
             event.bundle = getAppBundle(itemInfo);
             if (mContainer.mPlugin != null) {
@@ -207,7 +186,7 @@
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.startActivitySafely(this, itemInfo.getIntent(), itemInfo);
 
-        SearchTargetEvent event = getSearchTargetEvent(
+        SearchTargetEvent event = new SearchTargetEvent(
                 SearchTarget.ItemType.APP_HERO, eventType);
         event.bundle = getAppBundle(itemInfo);
         if (mPlugin != null) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 80f0981..3ec20d5 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.views;
 
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_FLAVOR;
-import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_LAUNCH_SOURCE;
 import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SETTINGS_BUTTON_TAP_OR_LONGPRESS;
@@ -133,7 +132,7 @@
             view.setOnLongClickListener(popup);
             popup.mItemMap.put(view, item);
         }
-        popup.show();
+        popup.reorderAndShow(popup.getChildCount());
     }
 
     @VisibleForTesting
@@ -212,8 +211,7 @@
         Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER)
                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                 .putExtra(EXTRA_WALLPAPER_OFFSET,
-                        launcher.getWorkspace().getWallpaperOffsetForCenterPage())
-                .putExtra(EXTRA_WALLPAPER_LAUNCH_SOURCE, "app_launched_launcher");
+                        launcher.getWorkspace().getWallpaperOffsetForCenterPage());
         if (!Utilities.existsStyleWallpapers(launcher)) {
             intent.putExtra(EXTRA_WALLPAPER_FLAVOR, "wallpaper_only");
         } else {
diff --git a/src/com/android/launcher3/views/SearchResultIconRow.java b/src/com/android/launcher3/views/SearchResultIconRow.java
deleted file mode 100644
index 313ae5e..0000000
--- a/src/com/android/launcher3/views/SearchResultIconRow.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
-import android.app.RemoteAction;
-import android.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.util.AttributeSet;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.allapps.AllAppsGridAdapter.AdapterItemWithPayload;
-import com.android.launcher3.allapps.search.AllAppsSearchBarController;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.model.data.RemoteActionItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
-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> {
-
-    private final Object[] mTargetInfo = createTargetInfo();
-    private ShortcutInfo mShortcutInfo;
-    private AllAppsSearchPlugin mPlugin;
-    private AdapterItemWithPayload<SearchTarget> mAdapterItem;
-
-
-    public SearchResultIconRow(@NonNull Context context) {
-        super(context);
-    }
-
-    public SearchResultIconRow(@NonNull Context context,
-            @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SearchResultIconRow(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
-        if (mAdapterItem != null) {
-            mAdapterItem.setSelectionHandler(null);
-        }
-        mAdapterItem = adapterItemWithPayload;
-        SearchTarget payload = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
-
-        if (payload.mRemoteAction != null) {
-            prepareUsingRemoteAction(payload.mRemoteAction,
-                    payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
-                    payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
-        } else {
-            prepareUsingShortcutInfo(payload.shortcuts.get(0));
-        }
-        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
-    }
-
-    private void prepareUsingShortcutInfo(ShortcutInfo shortcutInfo) {
-        mShortcutInfo = shortcutInfo;
-        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
-        applyFromWorkspaceItem(workspaceItemInfo);
-        LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
-        MODEL_EXECUTOR.execute(() -> {
-            launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
-            reapplyItemInfoAsync(workspaceItemInfo);
-        });
-    }
-
-    private void prepareUsingRemoteAction(RemoteAction remoteAction, String token, boolean start) {
-        RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(remoteAction, token, start);
-
-        applyFromRemoteActionInfo(itemInfo);
-        UI_HELPER_EXECUTOR.post(() -> {
-            // If the Drawable from the remote action is not AdaptiveBitmap, styling will not work.
-            try (LauncherIcons li = LauncherIcons.obtain(getContext())) {
-                Drawable d = itemInfo.getRemoteAction().getIcon().loadDrawable(getContext());
-                itemInfo.bitmap = li.createBadgedIconBitmap(d, itemInfo.user,
-                        Build.VERSION.SDK_INT);
-                reapplyItemInfoAsync(itemInfo);
-            }
-        });
-
-    }
-
-    void reapplyItemInfoAsync(ItemInfoWithIcon itemInfoWithIcon) {
-        MAIN_EXECUTOR.post(() -> reapplyItemInfo(itemInfoWithIcon));
-    }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
-    private 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());
-        }
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(searchTargetEvent);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/views/SearchResultPeopleView.java b/src/com/android/launcher3/views/SearchResultPeopleView.java
index 0c9a22f..6e45e88 100644
--- a/src/com/android/launcher3/views/SearchResultPeopleView.java
+++ b/src/com/android/launcher3/views/SearchResultPeopleView.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.views;
 
+import static android.content.Intent.URI_ALLOW_UNSAFE;
+import static android.content.Intent.URI_ANDROID_APP_SCHEME;
+
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
@@ -25,6 +28,7 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.View;
@@ -46,6 +50,7 @@
 import com.android.systemui.plugins.shared.SearchTarget;
 import com.android.systemui.plugins.shared.SearchTargetEvent;
 
+import java.net.URISyntaxException;
 import java.util.ArrayList;
 
 /**
@@ -61,8 +66,8 @@
     private TextView mTitleView;
     private ImageButton[] mProviderButtons = new ImageButton[3];
     private AllAppsSearchPlugin mPlugin;
-    private Intent mIntent;
-    private final Object[] mTargetInfo = createTargetInfo();
+    private Uri mContactUri;
+
 
     public SearchResultPeopleView(Context context) {
         this(context, null, 0);
@@ -104,7 +109,7 @@
         Bundle payload = adapterItemWithPayload.getPayload();
         mPlugin = adapterItemWithPayload.getPlugin();
         mTitleView.setText(payload.getString("title"));
-        mIntent = payload.getParcelable("intent");
+        mContactUri = payload.getParcelable("contact_uri");
         Bitmap icon = payload.getParcelable("icon");
         if (icon != null) {
             RoundedBitmapDrawable d = RoundedBitmapDrawableFactory.create(getResources(), icon);
@@ -120,20 +125,25 @@
         for (int i = 0; i < mProviderButtons.length; i++) {
             ImageButton button = mProviderButtons[i];
             if (providers != null && i < providers.size()) {
-                Bundle provider = providers.get(i);
-                Intent intent = provider.getParcelable("intent");
-                setupProviderButton(button, provider, intent, adapterItemWithPayload);
-                String pkg = provider.getString("package_name");
-                UI_HELPER_EXECUTOR.post(() -> {
-                    try {
-                        ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
-                                pkg, 0);
-                        Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
-                        MAIN_EXECUTOR.post(() -> button.setImageDrawable(appIcon));
-                    } catch (PackageManager.NameNotFoundException ignored) {
-                    }
+                try {
+                    Bundle provider = providers.get(i);
+                    Intent intent = Intent.parseUri(provider.getString("intent_uri_str"),
+                            URI_ANDROID_APP_SCHEME | URI_ALLOW_UNSAFE);
+                    setupProviderButton(button, provider, intent);
+                    String pkg = provider.getString("package_name");
+                    UI_HELPER_EXECUTOR.post(() -> {
+                        try {
+                            ApplicationInfo applicationInfo = mPackageManager.getApplicationInfo(
+                                    pkg, 0);
+                            Drawable appIcon = applicationInfo.loadIcon(mPackageManager);
+                            MAIN_EXECUTOR.post(()-> button.setImageDrawable(appIcon));
+                        } catch (PackageManager.NameNotFoundException ignored) {
+                        }
 
-                });
+                    });
+                } catch (URISyntaxException ex) {
+                    button.setVisibility(GONE);
+                }
             } else {
                 button.setVisibility(GONE);
             }
@@ -141,21 +151,15 @@
         adapterItemWithPayload.setSelectionHandler(this::handleSelection);
     }
 
-    @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(
+            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
                     SearchTarget.ItemType.PEOPLE,
                     SearchTargetEvent.CHILD_SELECT);
             searchTargetEvent.bundle = new Bundle();
-            searchTargetEvent.bundle.putParcelable("intent", mIntent);
+            searchTargetEvent.bundle.putParcelable("contact_uri", mContactUri);
             searchTargetEvent.bundle.putBundle("provider", provider);
             if (mPlugin != null) {
                 mPlugin.notifySearchTargetEvent(searchTargetEvent);
@@ -165,13 +169,14 @@
 
 
     private void handleSelection(int eventType) {
-        if (mIntent != null) {
+        if (mContactUri != null) {
             Launcher launcher = Launcher.getLauncher(getContext());
-            launcher.startActivitySafely(this, mIntent, null);
-            SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SearchTarget.ItemType.PEOPLE,
-                    eventType);
+            launcher.startActivitySafely(this, new Intent(Intent.ACTION_VIEW, mContactUri).setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK), null);
+            SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                    SearchTarget.ItemType.PEOPLE, eventType);
             searchTargetEvent.bundle = new Bundle();
-            searchTargetEvent.bundle.putParcelable("intent", mIntent);
+            searchTargetEvent.bundle.putParcelable("contact_uri", mContactUri);
             if (mPlugin != null) {
                 mPlugin.notifySearchTargetEvent(searchTargetEvent);
             }
diff --git a/src/com/android/launcher3/views/SearchResultPlayItem.java b/src/com/android/launcher3/views/SearchResultPlayItem.java
index ff3ecc8..8624609 100644
--- a/src/com/android/launcher3/views/SearchResultPlayItem.java
+++ b/src/com/android/launcher3/views/SearchResultPlayItem.java
@@ -58,8 +58,6 @@
     private String mPackageName;
     private boolean mIsInstantGame;
     private AllAppsSearchPlugin mPlugin;
-    private final Object[] mTargetInfo = createTargetInfo();
-
 
     public SearchResultPlayItem(Context context) {
         this(context, null, 0);
@@ -127,11 +125,6 @@
         });
     }
 
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
     private void showIfNecessary(TextView textView, @Nullable String string) {
         if (string == null || string.isEmpty()) {
             textView.setVisibility(GONE);
@@ -167,7 +160,7 @@
     }
 
     private void logSearchEvent(int eventType) {
-        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
                 SearchTarget.ItemType.PLAY_RESULTS, eventType);
         searchTargetEvent.bundle = new Bundle();
         searchTargetEvent.bundle.putString("package_name", mPackageName);
diff --git a/src/com/android/launcher3/views/SearchResultShortcut.java b/src/com/android/launcher3/views/SearchResultShortcut.java
new file mode 100644
index 0000000..307cf34
--- /dev/null
+++ b/src/com/android/launcher3/views/SearchResultShortcut.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.views;
+
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+
+import android.content.Context;
+import android.content.pm.ShortcutInfo;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsGridAdapter;
+import com.android.launcher3.allapps.search.AllAppsSearchBarController;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.touch.ItemClickHandler;
+import com.android.systemui.plugins.AllAppsSearchPlugin;
+import com.android.systemui.plugins.shared.SearchTarget;
+import com.android.systemui.plugins.shared.SearchTargetEvent;
+
+/**
+ * A view representing a stand alone shortcut search result
+ */
+public class SearchResultShortcut extends FrameLayout implements
+        AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
+
+    private BubbleTextView mBubbleTextView;
+    private View mIconView;
+    private ShortcutInfo mShortcutInfo;
+    private AllAppsSearchPlugin mPlugin;
+
+    public SearchResultShortcut(@NonNull Context context) {
+        super(context);
+    }
+
+    public SearchResultShortcut(@NonNull Context context,
+            @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public SearchResultShortcut(@NonNull Context context, @Nullable AttributeSet attrs,
+            int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        Launcher launcher = Launcher.getLauncher(getContext());
+        DeviceProfile grid = launcher.getDeviceProfile();
+        mIconView = findViewById(R.id.icon);
+        ViewGroup.LayoutParams iconParams = mIconView.getLayoutParams();
+        iconParams.height = grid.allAppsIconSizePx;
+        iconParams.width = grid.allAppsIconSizePx;
+        mBubbleTextView = findViewById(R.id.bubble_text);
+        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
+    }
+
+    @Override
+    public void applyAdapterInfo(
+            AllAppsGridAdapter.AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
+        SearchTarget payload = adapterItemWithPayload.getPayload();
+        mPlugin = adapterItemWithPayload.getPlugin();
+        mShortcutInfo = payload.shortcuts.get(0);
+        WorkspaceItemInfo workspaceItemInfo = new WorkspaceItemInfo(mShortcutInfo, getContext());
+        mBubbleTextView.applyFromWorkspaceItem(workspaceItemInfo);
+        mIconView.setBackground(mBubbleTextView.getIcon());
+        LauncherAppState launcherAppState = LauncherAppState.getInstance(getContext());
+        MODEL_EXECUTOR.execute(() -> {
+            launcherAppState.getIconCache().getShortcutIcon(workspaceItemInfo, mShortcutInfo);
+            mBubbleTextView.applyFromWorkspaceItem(workspaceItemInfo);
+            mIconView.setBackground(mBubbleTextView.getIcon());
+        });
+        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
+    }
+
+    private void handleSelection(int eventType) {
+        WorkspaceItemInfo itemInfo = (WorkspaceItemInfo) mBubbleTextView.getTag();
+        ItemClickHandler.onClickAppShortcut(this, itemInfo, Launcher.getLauncher(getContext()));
+
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
+                SearchTarget.ItemType.SHORTCUT, eventType);
+        searchTargetEvent.shortcut = mShortcutInfo;
+        if (mPlugin != null) {
+            mPlugin.notifySearchTargetEvent(searchTargetEvent);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/views/SearchResultSuggestRow.java b/src/com/android/launcher3/views/SearchResultSuggestRow.java
deleted file mode 100644
index b5abbcc..0000000
--- a/src/com/android/launcher3/views/SearchResultSuggestRow.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import static com.android.systemui.plugins.shared.SearchTarget.ItemType.SUGGEST;
-
-import android.content.Context;
-import android.os.Bundle;
-import android.text.TextUtils;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-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.model.data.ItemInfo;
-import com.android.launcher3.model.data.RemoteActionItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.systemui.plugins.AllAppsSearchPlugin;
-import com.android.systemui.plugins.shared.SearchTarget;
-import com.android.systemui.plugins.shared.SearchTargetEvent;
-
-/**
- * A view representing a fallback search suggestion row.
- */
-public class SearchResultSuggestRow extends LinearLayout implements
-        View.OnClickListener, AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
-
-    private final Object[] mTargetInfo = createTargetInfo();
-    private AllAppsSearchPlugin mPlugin;
-    private AdapterItemWithPayload<SearchTarget> mAdapterItem;
-    private TextView mTitle;
-
-
-    public SearchResultSuggestRow(@NonNull Context context) {
-        super(context);
-    }
-
-    public SearchResultSuggestRow(@NonNull Context context,
-            @Nullable AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public SearchResultSuggestRow(@NonNull Context context, @Nullable AttributeSet attrs,
-            int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mTitle = findViewById(R.id.title);
-        setOnClickListener(this);
-    }
-    @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItemWithPayload) {
-        mAdapterItem = adapterItemWithPayload;
-        SearchTarget payload = adapterItemWithPayload.getPayload();
-        mPlugin = adapterItemWithPayload.getPlugin();
-
-        if (payload.mRemoteAction != null) {
-            RemoteActionItemInfo itemInfo = new RemoteActionItemInfo(payload.mRemoteAction,
-                    payload.bundle.getString(SearchTarget.REMOTE_ACTION_TOKEN),
-                    payload.bundle.getBoolean(SearchTarget.REMOTE_ACTION_SHOULD_START));
-            setTag(itemInfo);
-        }
-        showIfAvailable(mTitle, payload.mRemoteAction.getTitle().toString());
-        setOnClickListener(v -> handleSelection(SearchTargetEvent.SELECT));
-        adapterItemWithPayload.setSelectionHandler(this::handleSelection);
-    }
-
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
-    private void handleSelection(int eventType) {
-        ItemInfo itemInfo = (ItemInfo) getTag();
-        Launcher launcher = Launcher.getLauncher(getContext());
-        if (itemInfo instanceof  RemoteActionItemInfo) return;
-
-        RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
-        ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
-        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(SUGGEST, 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());
-
-        if (mPlugin != null) {
-            mPlugin.notifySearchTargetEvent(searchTargetEvent);
-        }
-    }
-
-    @Override
-    public void onClick(View view) {
-        handleSelection(SearchTargetEvent.SELECT);
-    }
-
-    private void showIfAvailable(TextView view, @Nullable String string) {
-        System.out.println("Plugin suggest string:" + string);
-        if (TextUtils.isEmpty(string)) {
-            view.setVisibility(GONE);
-        } else {
-            System.out.println("Plugin suggest string:" + string);
-            view.setVisibility(VISIBLE);
-            view.setText(string);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/views/SearchSectionHeaderView.java b/src/com/android/launcher3/views/SearchSectionHeaderView.java
index 0fe0a43..d439ee3 100644
--- a/src/com/android/launcher3/views/SearchSectionHeaderView.java
+++ b/src/com/android/launcher3/views/SearchSectionHeaderView.java
@@ -52,9 +52,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..93bcee2 100644
--- a/src/com/android/launcher3/views/SearchSettingsRowView.java
+++ b/src/com/android/launcher3/views/SearchSettingsRowView.java
@@ -48,8 +48,6 @@
     private TextView mBreadcrumbsView;
     private Intent mIntent;
     private AllAppsSearchPlugin mPlugin;
-    private final Object[] mTargetInfo = createTargetInfo();
-
 
     public SearchSettingsRowView(@NonNull Context context) {
         super(context);
@@ -89,11 +87,6 @@
         adapterItemWithPayload.setSelectionHandler(this::handleSelection);
     }
 
-    @Override
-    public Object[] getTargetInfo() {
-        return mTargetInfo;
-    }
-
     private void showIfAvailable(TextView view, @Nullable String string) {
         if (TextUtils.isEmpty(string)) {
             view.setVisibility(GONE);
@@ -115,7 +108,7 @@
         Launcher launcher = Launcher.getLauncher(getContext());
         launcher.startActivityForResult(mIntent, 0);
 
-        SearchTargetEvent searchTargetEvent = getSearchTargetEvent(
+        SearchTargetEvent searchTargetEvent = new SearchTargetEvent(
                 SearchTarget.ItemType.SETTINGS_ROW, eventType);
         searchTargetEvent.bundle = new Bundle();
         searchTargetEvent.bundle.putParcelable("intent", mIntent);
diff --git a/src/com/android/launcher3/views/ThumbnailSearchResultView.java b/src/com/android/launcher3/views/ThumbnailSearchResultView.java
deleted file mode 100644
index bbc4773..0000000
--- a/src/com/android/launcher3/views/ThumbnailSearchResultView.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.views;
-
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.util.AttributeSet;
-
-import androidx.core.graphics.drawable.RoundedBitmapDrawable;
-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.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;
-
-/**
- * A view representing a high confidence app search result that includes shortcuts
- */
-public class ThumbnailSearchResultView extends androidx.appcompat.widget.AppCompatImageView
-        implements AllAppsSearchBarController.PayloadResultHandler<SearchTarget> {
-
-    private final Object[] mTargetInfo = createTargetInfo();
-    AllAppsSearchPlugin mPlugin;
-    int mPosition;
-
-    public ThumbnailSearchResultView(Context context) {
-        super(context);
-    }
-
-    public ThumbnailSearchResultView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ThumbnailSearchResultView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    private void handleSelection(int eventType) {
-        Launcher launcher = Launcher.getLauncher(getContext());
-        ItemInfo itemInfo = (ItemInfo) getTag();
-        if (itemInfo instanceof RemoteActionItemInfo) {
-            RemoteActionItemInfo remoteItemInfo = (RemoteActionItemInfo) itemInfo;
-            ItemClickHandler.onClickRemoteAction(launcher, remoteItemInfo);
-        } else {
-            ItemClickHandler.onClickAppShortcut(this, (WorkspaceItemInfo) itemInfo, launcher);
-        }
-        if (mPlugin != null) {
-            SearchTargetEvent event = getSearchTargetEvent(
-                    SearchTarget.ItemType.SCREENSHOT, eventType);
-            mPlugin.notifySearchTargetEvent(event);
-        }
-    }
-
-    @Override
-    public void applyAdapterInfo(AdapterItemWithPayload<SearchTarget> adapterItem) {
-        Launcher launcher = Launcher.getLauncher(getContext());
-        mPosition = adapterItem.position;
-
-        SearchTarget target = adapterItem.getPayload();
-        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));
-            ItemClickHandler.onClickRemoteAction(launcher, itemInfo);
-            bitmap = ((BitmapDrawable) target.mRemoteAction.getIcon()
-                    .loadDrawable(getContext())).getBitmap();
-            setTag(itemInfo);
-        } else {
-            bitmap = (Bitmap) target.bundle.getParcelable("bitmap");
-            WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
-            itemInfo.intent = new Intent(Intent.ACTION_VIEW)
-                    .setData(Uri.parse(target.bundle.getString("uri")))
-                    .setType("image/*")
-                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            setTag(itemInfo);
-        }
-        RoundedBitmapDrawable drawable = RoundedBitmapDrawableFactory.create(null, bitmap);
-        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;
-    }
-}
diff --git a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
index aa3ab8f..4fa670f 100644
--- a/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
+++ b/src_plugins/com/android/systemui/plugins/AllAppsSearchPlugin.java
@@ -32,7 +32,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 = 6;
 
     void setup(Activity activity, View view);
 
@@ -43,12 +43,6 @@
     void onStateTransitionComplete(int state);
 
     /**
-     * Send launcher window focus and visibility changed signals.
-     */
-    void onWindowFocusChanged(boolean hasFocus);
-    void onWindowVisibilityChanged(int visibility);
-
-    /**
      * Send signal when user starts typing, perform search, when search ends
      */
     void startedSearchSession();
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
index 3f0dc39..c6b8300 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTarget.java
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.plugins.shared;
 
-import android.app.RemoteAction;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 
@@ -26,69 +25,17 @@
  */
 public class SearchTarget implements Comparable<SearchTarget> {
 
-
-    /**
-     * 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";
-
-
     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);
+        PEOPLE(7);
 
         private final int mId;
-
         ViewType(int id) {
             mId = id;
         }
@@ -105,15 +52,9 @@
         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);
+        PEOPLE(6, "People", ViewType.PEOPLE);
 
         private final int mId;
-
-        /** Used to render section title. */
         private final String mTitle;
         private final ViewType mViewType;
 
@@ -140,21 +81,19 @@
     public List<ShortcutInfo> shortcuts;
     public Bundle bundle;
     public float score;
-    public String mSessionId;
-    public RemoteAction mRemoteAction;
 
     /**
      * 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) {
+            Bundle bundle, float score) {
         this.type = itemType;
         this.shortcuts = shortcuts;
         this.bundle = bundle;
         this.score = score;
-        this.mSessionId = sessionId;
     }
 
     @Override
diff --git a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
index 5016abc..ac4bc33 100644
--- a/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
+++ b/src_plugins/com/android/systemui/plugins/shared/SearchTargetEvent.java
@@ -15,7 +15,6 @@
  */
 package com.android.systemui.plugins.shared;
 
-import android.app.RemoteAction;
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 
@@ -30,17 +29,12 @@
 
     public SearchTarget.ItemType type;
     public ShortcutInfo shortcut;
-    public RemoteAction remoteAction;
     public int eventType;
     public Bundle bundle;
-    public int index;
-    public String sessionIdentifier;
+    public float score;
 
-    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType, int index,
-            String sessionId) {
+    public SearchTargetEvent(SearchTarget.ItemType itemType, int eventType) {
         this.type = itemType;
         this.eventType = eventType;
-        this.index = index;
-        this.sessionIdentifier = sessionId;
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index a4e53a1..ec3f93f 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -16,13 +16,13 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
 
 import android.content.Context;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Definition for AllApps state
@@ -41,7 +41,7 @@
     };
 
     public AllAppsState(int id) {
-        super(id, LAUNCHER_STATE_ALLAPPS, STATE_FLAGS);
+        super(id, ContainerType.ALLAPPS, STATE_FLAGS);
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
index da5a94f..d102bcc 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -15,11 +15,10 @@
  */
 package com.android.launcher3.uioverrides.states;
 
-import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-
 import android.content.Context;
 
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
  * Definition for overview state
@@ -27,7 +26,7 @@
 public class OverviewState extends LauncherState {
 
     public OverviewState(int id) {
-        super(id, LAUNCHER_STATE_OVERVIEW, FLAG_DISABLE_RESTORE);
+        super(id, ContainerType.WORKSPACE, FLAG_DISABLE_RESTORE);
     }
 
     @Override
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index e118481..5e42d9b 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -279,8 +279,6 @@
         if (userManager != null) {
             for (UserHandle userHandle : userManager.getUserProfiles()) {
                 if (!userHandle.isSystem()) {
-                    Log.d(TestProtocol.WORK_PROFILE_REMOVED,
-                            "removing user " + userHandle.getIdentifier());
                     mDevice.executeShellCommand("pm remove-user " + userHandle.getIdentifier());
                 }
             }
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 8d594de..f5f93c4 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -55,9 +55,7 @@
     private static final int WORK_PAGE = AllAppsContainerView.AdapterHolder.WORK;
 
     @Before
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    public void createWorkProfile() throws Exception {
         String output =
                 mDevice.executeShellCommand(
                         "pm create-user --profileOf 0 --managed TestProfile");
@@ -138,8 +136,7 @@
         });
 
         executeOnLauncher(launcher -> Log.d(TestProtocol.WORK_PROFILE_REMOVED,
-                "work profile status (" + mProfileUserId + ") :"
-                        + launcher.getAppsView().isWorkTabVisible()));
+                "Work profile status: " + launcher.getAppsView().isPersonalTabVisible()));
 
         // verify work edu is seen next
         waitForLauncherCondition("Launcher did not show the next edu screen", l ->