Merge "Events checking for long press events" into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index c7a0253..d7191b4 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -138,14 +138,6 @@
         </activity>
 
         <!--
-        Should point to the content provider which can be used to dump Launcher3 compatible
-        worspace configuration to the dump's file descriptor by using launcher_dump.proto
-        -->
-        <meta-data
-            android:name="com.android.launcher3.launcher_dump_provider"
-            android:value="com.android.launcher3.LauncherProvider" />
-
-        <!--
         The settings provider contains Home's data, like the workspace favorites. The permissions
         should be changed to what is defined above. The authorities should also be changed to
         represent the package name.
diff --git a/protos/launcher_atom.proto b/protos/launcher_atom.proto
new file mode 100644
index 0000000..a89fe5c
--- /dev/null
+++ b/protos/launcher_atom.proto
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+syntax = "proto2";
+
+option java_package = "com.android.launcher3.logger";
+option java_outer_classname = "LauncherAtom";
+
+//
+// ItemInfos
+message ItemInfo {
+  oneof Item {
+    Application application = 1;
+    Task task= 2;
+    Shortcut shortcut = 3;
+    Widget widget = 4;
+  }
+  // When used for launch event, stores the global predictive rank
+  optional int32 rank = 5;
+
+  // Stores whether the Item belows to non primary user
+  optional bool is_work = 6;
+
+  // Item can be child node to parent container or parent containers (nested)
+  oneof Container {
+    WorkspaceContainer workspace = 7;
+    HotseatContainer hotseat = 8;
+    FolderContainer folder = 9;
+  }
+  // Stores the origin of the Item
+  optional Origin source = 10;
+}
+
+enum Origin {
+  UNKNOWN = 0;
+  DEFAULT_LAYOUT = 1;       // icon automatically placed in workspace, folder, hotseat
+  BACKUP_RESTORE = 2;       // icon layout restored from backup
+  PINITEM = 3;              // from another app (e.g., Chrome's "Add to Home screen")
+  ALLAPPS_ATOZ = 4;         // within launcher surface, all aps a-z
+  WIDGETS = 5;              // within launcher, widgets tray
+  ADD_TO_HOMESCREEN = 6;    // play install + launcher home setting
+  ALLAPPS_PREDICTION = 7;   // from prediction bar in all apps container
+  HOTSEAT_PREDICTION = 8;   // from prediction bar in hotseat container
+}
+
+// Main app icons
+message Application {
+  optional string package_name = 1;
+  optional string component_name = 2;
+}
+
+// Legacy shortcuts and shortcuts handled by ShortcutManager
+message Shortcut {
+  optional string shortcut_name = 1;
+}
+
+// AppWidgets handled by AppWidgetManager
+message Widget {
+  optional int32 span_x = 1;
+  optional int32 span_y = 2;
+  optional int32 app_widget_id = 3;
+  optional string package_name = 4; // only populated during snapshot if from workspace
+  optional string component_name = 5; // only populated during snapshot if from workspace
+}
+
+// Tasks handled by PackageManager
+message Task {
+  optional string package_name = 1;
+  optional string component_name = 2;
+  optional int32 index = 3;
+}
+
+//////////////////////////////////////////////
+// Containers
+
+message WorkspaceContainer {
+  optional int32 page_index = 1; // range [-1, l], 0 is the index of the main homescreen
+  optional int32 grid_x = 2;     // [0, m], m varies based on the display density and resolution
+  optional int32 grid_y = 3;     // [0, n], n varies based on the display density and resolution
+}
+
+message HotseatContainer {
+  optional int32 index = 1;
+}
+
+message FolderContainer {
+  optional int32 page_index = 1;
+  optional int32 grid_x = 2;
+  optional int32 grid_y = 3;
+  oneof Container {
+    WorkspaceContainer workspace = 4;
+    HotseatContainer hotseat = 5;
+  }
+}
+
+
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 549187f..131b71f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -15,18 +15,22 @@
  */
 package com.android.launcher3.uioverrides;
 
-import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
+import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
+import android.view.View;
+import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.PendingAnimation;
@@ -44,7 +48,7 @@
 public final class RecentsViewStateController extends
         BaseRecentsViewStateController<LauncherRecentsView> {
 
-    public RecentsViewStateController(Launcher launcher) {
+    public RecentsViewStateController(BaseQuickstepLauncher launcher) {
         super(launcher);
     }
 
@@ -55,7 +59,7 @@
             mRecentsView.updateEmptyMessage();
             mRecentsView.resetTaskVisuals();
         }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state.getVisibleElements(mLauncher));
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, state, LINEAR);
         mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
     }
 
@@ -73,15 +77,22 @@
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        setAlphas(builder, toState.getVisibleElements(mLauncher));
+        setAlphas(builder, toState,
+                config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
                 toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
-    private void setAlphas(PropertySetter propertySetter, int visibleElements) {
-        boolean hasClearAllButton = (visibleElements & RECENTS_CLEAR_ALL_BUTTON) != 0;
+    private void setAlphas(PropertySetter propertySetter, LauncherState state,
+            Interpolator actionInterpolator) {
+        float buttonAlpha = (state.getVisibleElements(mLauncher) & OVERVIEW_BUTTONS) != 0 ? 1 : 0;
         propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                hasClearAllButton ? 1f : 0f, LINEAR);
+                buttonAlpha, LINEAR);
+
+        View actionsView = mLauncher.getActionsView();
+        if (actionsView != null) {
+            propertySetter.setViewAlpha(actionsView, buttonAlpha, actionInterpolator);
+        }
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index de3fce1..a87d6d1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -90,7 +90,7 @@
     @Override
     public int getVisibleElements(Launcher launcher) {
         return super.getVisibleElements(launcher)
-                & ~RECENTS_CLEAR_ALL_BUTTON & ~VERTICAL_SWIPE_INDICATOR;
+                & ~OVERVIEW_BUTTONS & ~VERTICAL_SWIPE_INDICATOR;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index 8087611..1288e7b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -25,6 +25,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.states.StateAnimationConfig;
 
 public class OverviewPeekState extends OverviewState {
@@ -37,6 +38,9 @@
         ScaleAndTranslation result = super.getOverviewScaleAndTranslation(launcher);
         result.translationX = NORMAL.getOverviewScaleAndTranslation(launcher).translationX
                 - launcher.getResources().getDimension(R.dimen.overview_peek_distance);
+        if (Utilities.isRtl(launcher.getResources())) {
+            result.translationX = -result.translationX;
+        }
         return result;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 024872f..bcfb11c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -164,17 +164,15 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        if (launcher.getDeviceProfile().isVerticalBarLayout()) {
-            return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
+        if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
+            return OVERVIEW_BUTTONS;
+        } else if (launcher.getDeviceProfile().isVerticalBarLayout()) {
+            return VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS;
         } else {
-            if (ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(launcher)) {
-                return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
-            }
-
             boolean hasAllAppsHeaderExtra = launcher.getAppsView() != null
                     && launcher.getAppsView().getFloatingHeaderView().hasVisibleContent();
-            return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON |
-                    (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
+            return HOTSEAT_SEARCH_BOX | VERTICAL_SWIPE_INDICATOR | OVERVIEW_BUTTONS
+                    | (hasAllAppsHeaderExtra ? ALL_APPS_HEADER_EXTRA : HOTSEAT_ICONS);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 7786a8f..113cdec 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -31,6 +31,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.Build;
+import android.util.Log;
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
@@ -48,6 +49,7 @@
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.model.PagedViewOrientedState;
 import com.android.launcher3.states.RotationHelper;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.touch.PortraitPagedViewHandler;
 import com.android.launcher3.util.VibratorWrapper;
@@ -198,18 +200,33 @@
     }
 
     protected void startNewTask(int successStateFlag, Consumer<Boolean> resultCallback) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask1");
+        }
         // Launch the task user scrolled to (mRecentsView.getNextPage()).
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             // We finish recents animation inside launchTask() when live tile is enabled.
             mRecentsView.getNextPageTaskView().launchTask(false /* animate */,
                     true /* freezeTaskList */);
         } else {
+            if (TestProtocol.sDebugTracing) {
+                Log.d(TestProtocol.NO_START_FROM_RECENTS, "startNewTask2");
+            }
             int taskId = mRecentsView.getNextPageTaskView().getTask().key.id;
             mFinishingRecentsAnimationForNewTaskId = taskId;
             mRecentsAnimationController.finish(true /* toRecents */, () -> {
+                if (TestProtocol.sDebugTracing) {
+                    Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete1");
+                }
                 if (!mCanceled) {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete2");
+                    }
                     TaskView nextTask = mRecentsView.getTaskView(taskId);
                     if (nextTask != null) {
+                        if (TestProtocol.sDebugTracing) {
+                            Log.d(TestProtocol.NO_START_FROM_RECENTS, "onFinishComplete3");
+                        }
                         nextTask.launchTask(false /* animate */, true /* freezeTaskList */,
                                 success -> {
                                     resultCallback.accept(success);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index ea5561b..da73bc0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -166,6 +166,7 @@
         super.onActivityInit(alreadyOnHome);
         mActivity = mActivityInterface.getCreatedActivity();
         mRecentsView = mActivity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         mRecentsView.setDisallowScrollToClearAll(true);
         mRecentsView.getClearAllButton().setVisibilityAlpha(0);
@@ -434,7 +435,12 @@
 
                 @Override
                 public void onAnimationSuccess(Animator animator) {
-                    finishAnimationTargetSetAnimationComplete();
+                    if (mRecentsView != null) {
+                        mRecentsView.setOnPageTransitionEndCallback(FallbackSwipeHandler.this
+                                ::finishAnimationTargetSetAnimationComplete);
+                    } else {
+                        finishAnimationTargetSetAnimationComplete();
+                    }
                     mFinishAnimation = null;
                 }
             };
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 5bac844..b3b0b02 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
+import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS;
 import static com.android.quickstep.util.ShelfPeekAnim.ShelfAnimState.HIDE;
@@ -46,6 +47,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.os.SystemClock;
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnApplyWindowInsetsListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -64,6 +66,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.logging.UserEventDispatcher;
+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;
@@ -244,7 +247,11 @@
                         | STATE_GESTURE_STARTED,
                 this::setupLauncherUiAfterSwipeUpToRecentsAnimation);
 
-        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED, this::onEndTargetSet);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED,
+                this::continueComputingRecentsScrollIfNecessary);
+        mGestureState.runOnceAtState(STATE_END_TARGET_ANIMATION_FINISHED
+                        | STATE_RECENTS_SCROLLING_FINISHED,
+                this::onSettledOnEndTarget);
 
         mStateCallback.runOnceAtState(STATE_HANDLER_INVALIDATED, this::invalidateHandler);
         mStateCallback.runOnceAtState(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
@@ -283,6 +290,7 @@
         }
 
         mRecentsView = activity.getOverviewPanel();
+        mRecentsView.setOnPageTransitionEndCallback(null);
         linkRecentsViewScroll();
         addLiveTileOverlay();
 
@@ -505,16 +513,22 @@
     }
 
     private void buildAnimationController() {
-        if (mGestureState.getEndTarget() == HOME || mHasLauncherTransitionControllerStarted) {
-            // We don't want a new mLauncherTransitionController if
-            // mGestureState.getEndTarget() == HOME (it has its own animation) or if we're already
-            // animating the current controller.
+        if (!canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         initTransitionEndpoints(mActivity.getDeviceProfile());
         mAnimationFactory.createActivityInterface(mTransitionDragLength);
     }
 
+    /**
+     * We don't want to change mLauncherTransitionController if mGestureState.getEndTarget() == HOME
+     * (it has its own animation) or if we're already animating the current controller.
+     * @return Whether we can create the launcher controller or update its progress.
+     */
+    private boolean canCreateNewOrUpdateExistingLauncherTransitionController() {
+        return mGestureState.getEndTarget() != HOME && !mHasLauncherTransitionControllerStarted;
+    }
+
     @Override
     public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
         WindowInsets result = view.onApplyWindowInsets(windowInsets);
@@ -558,15 +572,12 @@
             }
         }
 
-        if (mLauncherTransitionController == null || mLauncherTransitionController
-                .getAnimationPlayer().isStarted()) {
-            return;
-        }
         updateLauncherTransitionProgress();
     }
 
     private void updateLauncherTransitionProgress() {
-        if (mGestureState.getEndTarget() == HOME) {
+        if (mLauncherTransitionController == null
+                || !canCreateNewOrUpdateExistingLauncherTransitionController()) {
             return;
         }
         // Normalize the progress to 0 to 1, as the animation controller will clamp it to that
@@ -696,7 +707,7 @@
         }
     }
 
-    private void onEndTargetSet() {
+    private void onSettledOnEndTarget() {
         switch (mGestureState.getEndTarget()) {
             case HOME:
                 mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
@@ -859,13 +870,17 @@
             if (mDeviceState.isFullyGesturalNavMode()) {
                 setShelfState(ShelfAnimState.OVERVIEW, interpolator, duration);
             }
-        } else if (endTarget == NEW_TASK || endTarget == LAST_TASK) {
-            // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
-            // or resumeLastTask().
-            if (mRecentsView != null) {
-                duration = Math.max(duration, mRecentsView.getScroller().getDuration());
-            }
         }
+
+        // Let RecentsView handle the scrolling to the task, which we launch in startNewTask()
+        // or resumeLastTask().
+        if (mRecentsView != null) {
+            mRecentsView.setOnPageTransitionEndCallback(
+                    () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED));
+        } else {
+            mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED);
+        }
+
         animateToProgress(startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
@@ -949,15 +964,14 @@
             ValueAnimator windowAnim = mCurrentShift.animateToValue(start, end);
             windowAnim.setDuration(duration).setInterpolator(interpolator);
             windowAnim.addUpdateListener(valueAnimator -> {
-                if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
-                    // Views typically don't compute scroll when invisible as an optimization,
-                    // but in our case we need to since the window offset depends on the scroll.
-                    mRecentsView.computeScroll();
-                }
+                computeRecentsScrollIfInvisible();
             });
             windowAnim.addListener(new AnimationSuccessListener() {
                 @Override
                 public void onAnimationSuccess(Animator animator) {
+                    if (TestProtocol.sDebugTracing) {
+                        Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationSuccess");
+                    }
                     if (mRecentsAnimationController == null) {
                         // If the recents animation is interrupted, we still end the running
                         // animation (not canceled) so this is still called. In that case, we can
@@ -1005,6 +1019,21 @@
         mHasLauncherTransitionControllerStarted = true;
     }
 
+    private void computeRecentsScrollIfInvisible() {
+        if (mRecentsView != null && mRecentsView.getVisibility() != View.VISIBLE) {
+            // Views typically don't compute scroll when invisible as an optimization,
+            // but in our case we need to since the window offset depends on the scroll.
+            mRecentsView.computeScroll();
+        }
+    }
+
+    private void continueComputingRecentsScrollIfNecessary() {
+        if (!mGestureState.hasState(STATE_RECENTS_SCROLLING_FINISHED)) {
+            computeRecentsScrollIfInvisible();
+            mRecentsView.post(this::continueComputingRecentsScrollIfNecessary);
+        }
+    }
+
     /**
      * Creates an animation that transforms the current app window into the home app.
      * @param startProgress The progress of {@link #mCurrentShift} to start the window from.
@@ -1166,6 +1195,9 @@
     }
 
     private void switchToScreenshot() {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "switchToScreenshot");
+        }
         final int runningTaskId = mGestureState.getRunningTaskId();
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             if (mRecentsAnimationController != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index 24703bd..e62de18 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.ALL_APPS_HEADER_EXTRA;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.RECENTS_CLEAR_ALL_BUTTON;
+import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.QuickstepAppTransitionManagerImpl.ALL_APPS_PROGRESS_OFF_SCREEN;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -323,7 +323,7 @@
         if (enabled) {
             LauncherState state = mActivity.getStateManager().getState();
             boolean hasClearAllButton = (state.getVisibleElements(mActivity)
-                    & RECENTS_CLEAR_ALL_BUTTON) != 0;
+                    & OVERVIEW_BUTTONS) != 0;
             setDisallowScrollToClearAll(!hasClearAllButton);
         }
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 57a9940..1c95a9e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -30,6 +30,8 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_DISMISS_SWIPE_UP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.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;
@@ -1183,13 +1185,13 @@
                 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
     }
 
-    private void removeTask(Task task, int index, EndState endState) {
-        if (task != null) {
-            ActivityManagerWrapper.getInstance().removeTask(task.key.id);
-            ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key);
+    private void removeTask(TaskView taskView, int index, EndState endState) {
+        if (taskView.getTask() != null) {
+            ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id);
+            ComponentKey compKey = TaskUtils.getLaunchComponentKeyForTask(taskView.getTask().key);
             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
-                    endState.logAction, Direction.UP, index, componentKey);
-            mActivity.getStatsLogManager().logTaskDismiss(this, componentKey);
+                    endState.logAction, Direction.UP, index, compKey);
+            mActivity.getStatsLogManager().log(TASK_DISMISS_SWIPE_UP, taskView.buildProto());
         }
     }
 
@@ -1284,7 +1286,7 @@
             private void onEnd(EndState endState) {
                 if (endState.isSuccess) {
                     if (shouldRemoveTask) {
-                        removeTask(taskView.getTask(), draggedIndex, endState);
+                        removeTask(taskView, draggedIndex, endState);
                     }
 
                     int pageToSnapTo = mCurrentPage;
@@ -1733,6 +1735,8 @@
                     mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                             endState.logAction, Direction.DOWN, indexOfChild(tv),
                             TaskUtils.getLaunchComponentKeyForTask(task.key));
+                    mActivity.getStatsLogManager().log(TASK_LAUNCH_SWIPE_DOWN, tv.buildProto()
+                    );
                 }
             } else {
                 onTaskLaunched(false);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 56e3632..8160aae 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.TASK_LAUNCH_TAP;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -43,6 +44,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Process;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.Log;
@@ -59,6 +61,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.states.RotationHelper;
@@ -68,6 +71,7 @@
 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.ViewPool.Reusable;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.TaskIconCache;
@@ -217,8 +221,7 @@
             mActivity.getUserEventDispatcher().logTaskLaunchOrDismiss(
                     Touch.TAP, Direction.NONE, getRecentsView().indexOfChild(this),
                     TaskUtils.getLaunchComponentKeyForTask(getTask().key));
-            mActivity.getStatsLogManager().logTaskLaunch(getRecentsView(),
-                    TaskUtils.getLaunchComponentKeyForTask(getTask().key));
+            mActivity.getStatsLogManager().log(TASK_LAUNCH_TAP, buildProto());
         });
         mCornerRadius = TaskCornerRadius.get(context);
         mWindowCornerRadius = QuickStepContract.getWindowCornerRadius(context.getResources());
@@ -229,6 +232,17 @@
         setOutlineProvider(mOutlineProvider);
     }
 
+    /* Builds proto for logging */
+    protected LauncherAtom.ItemInfo buildProto() {
+        ComponentKey componentKey = TaskUtils.getLaunchComponentKeyForTask(getTask().key);
+        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+        itemBuilder.setIsWork(componentKey.user != Process.myUserHandle());
+        itemBuilder.setTask(LauncherAtom.Task.newBuilder()
+                .setComponentName(componentKey.componentName.flattenToShortString())
+                .setIndex(getRecentsView().indexOfChild(this)));
+        return itemBuilder.build();
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 40d7c7a..f0f8933 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -116,7 +116,7 @@
 
     <!-- ******* Overview ******* -->
     <!-- Label for a button that causes the current overview app to be shared. [CHAR_LIMIT=40] -->
-    <string translatable="false" name="action_share">Share</string>
+    <string name="action_share">Share</string>
     <!-- Label for a button that causes a screen shot of the current app to be taken. [CHAR_LIMIT=40] -->
-    <string translatable="false" name="action_screenshot">Screenshot</string>
+    <string name="action_screenshot">Screenshot</string>
 </resources>
\ No newline at end of file
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 83c67bb..135daef 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -34,6 +34,7 @@
 import android.content.IntentSender;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.view.View;
 
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -80,6 +81,8 @@
 
     private final ShelfPeekAnim mShelfPeekAnim = new ShelfPeekAnim(this);
 
+    private View mActionsView;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -224,16 +227,22 @@
     @Override
     protected void setupViews() {
         super.setupViews();
+        mActionsView = findViewById(R.id.overview_actions_view);
+
 
         if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get() && removeShelfFromOverview(this)) {
             // Overview is above all other launcher elements, including qsb, so move it to the top.
             getOverviewPanel().bringToFront();
-            if (getActionsView() != null) {
-                getActionsView().bringToFront();
+            if (mActionsView != null) {
+                mActionsView.bringToFront();
             }
         }
     }
 
+    public View getActionsView() {
+        return mActionsView;
+    }
+
     @Override
     protected void closeOpenViews(boolean animate) {
         super.closeOpenViews(animate);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 03454f7..123c988 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
@@ -34,11 +33,10 @@
 
 import android.util.FloatProperty;
 import android.view.View;
-import android.view.animation.Interpolator;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.LauncherStateManager.StateHandler;
@@ -55,13 +53,11 @@
 public abstract class BaseRecentsViewStateController<T extends View>
         implements StateHandler {
     protected final T mRecentsView;
-    protected final Launcher mLauncher;
-    protected final View mActionsView;
+    protected final BaseQuickstepLauncher mLauncher;
 
-    public BaseRecentsViewStateController(@NonNull Launcher launcher) {
+    public BaseRecentsViewStateController(@NonNull BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
         mRecentsView = launcher.getOverviewPanel();
-        mActionsView = launcher.getActionsView();
     }
 
     @Override
@@ -69,19 +65,12 @@
         ScaleAndTranslation scaleAndTranslation = state
                 .getOverviewScaleAndTranslation(mLauncher);
         SCALE_PROPERTY.set(mRecentsView, scaleAndTranslation.scale);
-        float translationX = scaleAndTranslation.translationX;
-        if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            translationX = -translationX;
-        }
-        mRecentsView.setTranslationX(translationX);
+        mRecentsView.setTranslationX(scaleAndTranslation.translationX);
         mRecentsView.setTranslationY(scaleAndTranslation.translationY);
+
         getContentAlphaProperty().set(mRecentsView, state.overviewUi ? 1f : 0);
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         SCRIM_PROGRESS.set(scrim, state.getOverviewScrimAlpha(mLauncher));
-        if (mActionsView != null) {
-            mActionsView.setTranslationX(translationX);
-            mActionsView.setAlpha(state.overviewUi ? 1f : 0);
-        }
     }
 
     @Override
@@ -107,29 +96,18 @@
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
             @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
         ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
-        Interpolator scaleInterpolator = config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
-        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
-        Interpolator translateXInterpolator = config.getInterpolator(
-                ANIM_OVERVIEW_TRANSLATE_X, LINEAR);
-        Interpolator translateYInterpolator = config.getInterpolator(
-                ANIM_OVERVIEW_TRANSLATE_Y, LINEAR);
-        float translationX = scaleAndTranslation.translationX;
-        if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
-            translationX = -translationX;
-        }
-        setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
+        setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale,
+                config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
+        setter.setFloat(mRecentsView, VIEW_TRANSLATE_X, scaleAndTranslation.translationX,
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
         setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
-                translateYInterpolator);
+                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
+
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
                 config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
                 config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
-        if (mActionsView != null) {
-            setter.setFloat(mActionsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
-            setter.setFloat(mActionsView, VIEW_ALPHA, toState.overviewUi ? 1 : 0,
-                    config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
-        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index f7e40ca..631df4c 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -105,6 +105,10 @@
     public static final int STATE_RECENTS_ANIMATION_ENDED =
             getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED");
 
+    // Called when RecentsView stops scrolling and settles on a TaskView.
+    public static final int STATE_RECENTS_SCROLLING_FINISHED =
+            getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED");
+
 
     // Needed to interact with the current activity
     private final Intent mHomeIntent;
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 0f98b32..783978d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -19,11 +19,13 @@
 
 import android.graphics.Rect;
 import android.util.ArraySet;
+import android.util.Log;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.Preconditions;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -89,6 +91,9 @@
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
             Rect homeContentInsets, Rect minimizedHomeBounds) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "onAnimationStart");
+        }
         RecentsAnimationTargets targets = new RecentsAnimationTargets(appTargets,
                 wallpaperTargets, homeContentInsets, minimizedHomeBounds);
         mController = new RecentsAnimationController(animationController,
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 6902e37..b04a1ae 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -26,6 +26,7 @@
 
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 
@@ -52,6 +53,9 @@
     @UiThread
     public RecentsAnimationCallbacks startRecentsAnimation(GestureState gestureState,
             Intent intent, RecentsAnimationCallbacks.RecentsAnimationListener listener) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_START_FROM_RECENTS, "startRecentsAnimation");
+        }
         // Notify if recents animation is still running
         if (mController != null) {
             String msg = "New recents animation started before old animation completed";
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 22fe2e1..58bb980 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -18,36 +18,22 @@
 
 import static android.stats.launcher.nano.Launcher.ALLAPPS;
 import static android.stats.launcher.nano.Launcher.BACKGROUND;
-import static android.stats.launcher.nano.Launcher.DISMISS_TASK;
 import static android.stats.launcher.nano.Launcher.HOME;
-import static android.stats.launcher.nano.Launcher.LAUNCH_APP;
-import static android.stats.launcher.nano.Launcher.LAUNCH_TASK;
 import static android.stats.launcher.nano.Launcher.OVERVIEW;
 
-import static com.android.launcher3.logging.UserEventDispatcher.makeTargetsList;
-
 import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.stats.launcher.nano.Launcher;
-import android.stats.launcher.nano.LauncherExtension;
-import android.stats.launcher.nano.LauncherTarget;
-import android.util.Log;
-import android.view.View;
 
-import androidx.annotation.Nullable;
-
+import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogUtils;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.shared.system.SysUiStatsLog;
-
-import com.google.protobuf.nano.MessageNano;
+import com.android.launcher3.model.AllAppsList;
+import com.android.launcher3.model.BaseModelUpdateTask;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.util.IntSparseArrayMap;
 
 import java.util.ArrayList;
 
@@ -62,186 +48,17 @@
 public class StatsLogCompatManager extends StatsLogManager {
 
     private static final int SUPPORTED_TARGET_DEPTH = 2;
-    private static final String TAG = "StatsLogCompatManager";
+    private static final String TAG = "StatsLog";
     private static final boolean DEBUG = false;
+    private static Context sContext;
 
     public StatsLogCompatManager(Context context) {
+        sContext = context;
     }
 
     @Override
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = mStateProvider.getCurrentState();
-        fillInLauncherExtension(v, ext);
-        if (ext.srcTarget[0] != null) {
-            ext.srcTarget[0].item = LauncherTarget.APP_ICON;
-        }
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_APP, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logTaskLaunch(View v, ComponentKey componentKey) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = OVERVIEW;
-        fillInLauncherExtension(v, ext);
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_TASK, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logTaskDismiss(View v, ComponentKey componentKey) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
-        int srcState = OVERVIEW;
-        fillInLauncherExtension(v, ext);
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, DISMISS_TASK, srcState,
-                BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
-    }
-
-    @Override
-    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) {
-        LauncherExtension ext = new LauncherExtension();
-        ext.srcTarget = new LauncherTarget[1];
-        int srcState = mStateProvider.getCurrentState();
-        fillInLauncherExtensionWithPageId(ext, pageId);
-        int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
-        SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, launcherAction, srcState, srcState,
-                MessageNano.toByteArray(ext), true);
-    }
-
-    public static boolean fillInLauncherExtension(View v, LauncherExtension extension) {
-        if (DEBUG) {
-            Log.d(TAG, "fillInLauncherExtension");
-        }
-
-        StatsLogUtils.LogContainerProvider provider = StatsLogUtils.getLaunchProviderRecursive(v);
-        if (v == null || !(v.getTag() instanceof ItemInfo) || provider == null) {
-            if (DEBUG) {
-                Log.d(TAG, "View or provider is null, or view doesn't have an ItemInfo tag.");
-            }
-
-            return false;
-        }
-        Target child = new Target();
-        ArrayList<Target> targets = makeTargetsList(child);
-        targets.add(child);
-        provider.fillInLogContainerData((ItemInfo) v.getTag(), child, targets);
-
-        int maxDepth = Math.min(SUPPORTED_TARGET_DEPTH, targets.size());
-        extension.srcTarget = new LauncherTarget[maxDepth];
-        for (int i = 0; i < maxDepth; i++) {
-            extension.srcTarget[i] = new LauncherTarget();
-            copy(targets.get(i), extension.srcTarget[i]);
-        }
-        return true;
-    }
-
-    public static boolean fillInLauncherExtensionWithPageId(LauncherExtension ext, int pageId) {
-        if (DEBUG) {
-            Log.d(TAG, "fillInLauncherExtensionWithPageId, pageId = " + pageId);
-        }
-
-        Target target = new Target();
-        target.pageIndex = pageId;
-        ext.srcTarget[0] = new LauncherTarget();
-        copy(target, ext.srcTarget[0]);
-        return true;
-    }
-
-    private static void copy(Target src, LauncherTarget dst) {
-        if (DEBUG) {
-            Log.d(TAG, "copy target information from clearcut Target to LauncherTarget.");
-        }
-
-        // Fill in type
-        switch (src.type) {
-            case Target.Type.ITEM:
-                dst.type = LauncherTarget.ITEM_TYPE;
-                break;
-            case Target.Type.CONTROL:
-                dst.type = LauncherTarget.CONTROL_TYPE;
-                break;
-            case Target.Type.CONTAINER:
-                dst.type = LauncherTarget.CONTAINER_TYPE;
-                break;
-            default:
-                dst.type = LauncherTarget.NONE;
-                break;
-        }
-
-        // Fill in item
-        switch (src.itemType) {
-            case ItemType.APP_ICON:
-                dst.item = LauncherTarget.APP_ICON;
-                break;
-            case ItemType.SHORTCUT:
-                dst.item = LauncherTarget.SHORTCUT;
-                break;
-            case ItemType.WIDGET:
-                dst.item = LauncherTarget.WIDGET;
-                break;
-            case ItemType.FOLDER_ICON:
-                dst.item = LauncherTarget.FOLDER_ICON;
-                break;
-            case ItemType.DEEPSHORTCUT:
-                dst.item = LauncherTarget.DEEPSHORTCUT;
-                break;
-            case ItemType.SEARCHBOX:
-                dst.item = LauncherTarget.SEARCHBOX;
-                break;
-            case ItemType.EDITTEXT:
-                dst.item = LauncherTarget.EDITTEXT;
-                break;
-            case ItemType.NOTIFICATION:
-                dst.item = LauncherTarget.NOTIFICATION;
-                break;
-            case ItemType.TASK:
-                dst.item = LauncherTarget.TASK;
-                break;
-            default:
-                dst.item = LauncherTarget.DEFAULT_ITEM;
-                break;
-        }
-
-        // Fill in container
-        switch (src.containerType) {
-            case ContainerType.HOTSEAT:
-                dst.container = LauncherTarget.HOTSEAT;
-                break;
-            case ContainerType.FOLDER:
-                dst.container = LauncherTarget.FOLDER;
-                break;
-            case ContainerType.PREDICTION:
-                dst.container = LauncherTarget.PREDICTION;
-                break;
-            case ContainerType.SEARCHRESULT:
-                dst.container = LauncherTarget.SEARCHRESULT;
-                break;
-            default:
-                dst.container = LauncherTarget.DEFAULT_CONTAINER;
-                break;
-        }
-
-        // Fill in control
-        switch (src.controlType) {
-            case ControlType.UNINSTALL_TARGET:
-                dst.control = LauncherTarget.UNINSTALL;
-                break;
-            case ControlType.REMOVE_TARGET:
-                dst.control = LauncherTarget.REMOVE;
-                break;
-            default:
-                dst.control = LauncherTarget.DEFAULT_CONTROL;
-                break;
-        }
-
-        // Fill in other fields
-        dst.pageId = src.pageIndex;
-        dst.gridX = src.gridX;
-        dst.gridY = src.gridY;
+    public void log(LauncherEvent eventId, LauncherAtom.ItemInfo item) {
+        // Call StatsLog method
     }
 
     @Override
@@ -254,4 +71,36 @@
                     "StatsLogUtil constants doesn't match enums in launcher.proto");
         }
     }
+
+    /**
+     * Logs the workspace layout information on the model thread.
+     */
+    public void logSnapshot() {
+        LauncherAppState.getInstance(sContext).getModel().enqueueModelUpdateTask(
+                new SnapshotWorker());
+    }
+
+    private class SnapshotWorker extends BaseModelUpdateTask {
+        @Override
+        public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
+            IntSparseArrayMap<FolderInfo> folders = dataModel.folders.clone();
+            ArrayList<ItemInfo> workspaceItems = (ArrayList) dataModel.workspaceItems.clone();
+            ArrayList<LauncherAppWidgetInfo> appWidgets = (ArrayList) dataModel.appWidgets.clone();
+
+            for (ItemInfo info : workspaceItems) {
+                LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
+                // call StatsLog method
+            }
+            for (FolderInfo fInfo : folders) {
+                for (ItemInfo info : fInfo.contents) {
+                    LauncherAtom.ItemInfo atomInfo = info.buildProto(null, fInfo);
+                    // call StatsLog method
+                }
+            }
+            for (ItemInfo info : appWidgets) {
+                LauncherAtom.ItemInfo atomInfo = info.buildProto(null, null);
+                // call StatsLog method
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 9f3b48f..6fa3c28 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.APP_LAUNCH_TAP;
 import static com.android.launcher3.util.DefaultDisplay.CHANGE_ROTATION;
 
 import android.app.ActivityOptions;
@@ -181,7 +182,7 @@
                         sourceContainer);
             }
             getUserEventDispatcher().logAppLaunch(v, intent, user);
-            getStatsLogManager().logAppLaunch(v, intent, user);
+            getStatsLogManager().log(APP_LAUNCH_TAP, item.buildProto(null, null));
             return true;
         } catch (NullPointerException|ActivityNotFoundException|SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 21a8fd4..c8e73ba 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -39,7 +39,6 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.widget.TextView;
 
@@ -109,8 +108,6 @@
     private final int mDisplay;
 
     private final CheckLongPressHelper mLongPressHelper;
-    private final StylusEventHelper mStylusEventHelper;
-    private final float mSlop;
 
     private final boolean mLayoutHorizontal;
     private final int mIconSize;
@@ -137,9 +134,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mDisableRelayout = false;
 
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private final boolean mIgnorePaddingTouch;
-
     private IconLoadRequest mIconLoadRequest;
 
     public BubbleTextView(Context context) {
@@ -153,7 +147,6 @@
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mActivity = ActivityContext.lookupContext(context);
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
@@ -166,23 +159,19 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
-            mIgnorePaddingTouch = true;
         } else if (mDisplay == DISPLAY_ALL_APPS) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
             defaultIconSize = grid.allAppsIconSizePx;
-            mIgnorePaddingTouch = true;
         } else if (mDisplay == DISPLAY_FOLDER) {
             DeviceProfile grid = mActivity.getDeviceProfile();
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx);
             setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx);
             defaultIconSize = grid.folderChildIconSizePx;
-            mIgnorePaddingTouch = true;
         } else {
             // widget_selection or shortcut_popup
             defaultIconSize = mActivity.getDeviceProfile().iconSizePx;
-            mIgnorePaddingTouch = false;
         }
 
         mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false);
@@ -192,7 +181,6 @@
         a.recycle();
 
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
         mDotParams = new DotRenderer.DrawParams();
 
@@ -333,42 +321,21 @@
     @Override
     public boolean onTouchEvent(MotionEvent event) {
         // ignore events if they happen in padding area
-        if (event.getAction() == MotionEvent.ACTION_DOWN && mIgnorePaddingTouch
+        if (event.getAction() == MotionEvent.ACTION_DOWN
                 && (event.getY() < getPaddingTop()
                 || event.getX() < getPaddingLeft()
                 || event.getY() > getHeight() - getPaddingBottom()
                 || event.getX() > getWidth() - getPaddingRight())) {
             return false;
         }
-
-        // Call the superclass onTouchEvent first, because sometimes it changes the state to
-        // isPressed() on an ACTION_UP
-        boolean result = super.onTouchEvent(event);
-
-        // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.onMotionEvent(event)) {
-            mLongPressHelper.cancelLongPress();
-            result = true;
+        if (isLongClickable()) {
+            super.onTouchEvent(event);
+            mLongPressHelper.onTouchEvent(event);
+            // Keep receiving the rest of the events
+            return true;
+        } else {
+            return super.onTouchEvent(event);
         }
-
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                // If we're in a stylus button press, don't check for long press.
-                if (!mStylusEventHelper.inStylusButtonPressed()) {
-                    mLongPressHelper.postCheckForLongPress();
-                }
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
-        return result;
     }
 
     void setStayPressed(boolean stayPressed) {
@@ -531,7 +498,6 @@
     @Override
     public void cancelLongPress() {
         super.cancelLongPress();
-
         mLongPressHelper.cancelLongPress();
     }
 
diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java
index 639c173..ef353f9 100644
--- a/src/com/android/launcher3/CheckLongPressHelper.java
+++ b/src/com/android/launcher3/CheckLongPressHelper.java
@@ -16,46 +16,68 @@
 
 package com.android.launcher3;
 
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 
-import com.android.launcher3.util.Thunk;
-
+/**
+ * Utility class to handle tripper long press on a view with custom timeout and stylus event
+ */
 public class CheckLongPressHelper {
 
     public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f;
 
-    @Thunk View mView;
-    @Thunk View.OnLongClickListener mListener;
-    @Thunk boolean mHasPerformedLongPress;
-    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
-    private CheckForLongPress mPendingCheckForLongPress;
+    private final View mView;
+    private final View.OnLongClickListener mListener;
+    private final float mSlop;
 
-    class CheckForLongPress implements Runnable {
-        public void run() {
-            if ((mView.getParent() != null) && mView.hasWindowFocus()
-                    && !mHasPerformedLongPress) {
-                boolean handled;
-                if (mListener != null) {
-                    handled = mListener.onLongClick(mView);
-                } else {
-                    handled = mView.performLongClick();
-                }
-                if (handled) {
-                    mView.setPressed(false);
-                    mHasPerformedLongPress = true;
-                }
-            }
-        }
-    }
+    private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR;
+
+    private boolean mHasPerformedLongPress;
+
+    private Runnable mPendingCheckForLongPress;
 
     public CheckLongPressHelper(View v) {
-        mView = v;
+        this(v, null);
     }
 
     public CheckLongPressHelper(View v, View.OnLongClickListener listener) {
         mView = v;
         mListener = listener;
+        mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
+    }
+
+    /**
+     * Handles the touch event on a view
+     *
+     * @see View#onTouchEvent(MotionEvent)
+     */
+    public void onTouchEvent(MotionEvent ev) {
+        switch (ev.getAction()) {
+            case MotionEvent.ACTION_DOWN: {
+                // Just in case the previous long press hasn't been cleared, we make sure to
+                // start fresh on touch down.
+                cancelLongPress();
+
+                postCheckForLongPress();
+                if (isStylusButtonPressed(ev)) {
+                    triggerLongPress();
+                }
+                break;
+            }
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                cancelLongPress();
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (!Utilities.pointInView(mView, ev.getX(), ev.getY(), mSlop)) {
+                    cancelLongPress();
+                } else if (mPendingCheckForLongPress != null && isStylusButtonPressed(ev)) {
+                    // Only trigger long press if it has not been cancelled before
+                    triggerLongPress();
+                }
+                break;
+        }
     }
 
     /**
@@ -65,25 +87,64 @@
         mLongPressTimeoutFactor = longPressTimeoutFactor;
     }
 
-    public void postCheckForLongPress() {
+    private void postCheckForLongPress() {
         mHasPerformedLongPress = false;
 
         if (mPendingCheckForLongPress == null) {
-            mPendingCheckForLongPress = new CheckForLongPress();
+            mPendingCheckForLongPress = this::triggerLongPress;
         }
         mView.postDelayed(mPendingCheckForLongPress,
                 (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor));
     }
 
+    /**
+     * Cancels any pending long press
+     */
     public void cancelLongPress() {
         mHasPerformedLongPress = false;
+        clearCallbacks();
+    }
+
+    /**
+     * Returns true if long press has been performed in the current touch gesture
+     */
+    public boolean hasPerformedLongPress() {
+        return mHasPerformedLongPress;
+    }
+
+    private void triggerLongPress() {
+        if ((mView.getParent() != null) && mView.hasWindowFocus() && !mHasPerformedLongPress) {
+            boolean handled;
+            if (mListener != null) {
+                handled = mListener.onLongClick(mView);
+            } else {
+                handled = mView.performLongClick();
+            }
+            if (handled) {
+                mView.setPressed(false);
+                mHasPerformedLongPress = true;
+            }
+            clearCallbacks();
+        }
+    }
+
+    private void clearCallbacks() {
         if (mPendingCheckForLongPress != null) {
             mView.removeCallbacks(mPendingCheckForLongPress);
             mPendingCheckForLongPress = null;
         }
     }
 
-    public boolean hasPerformedLongPress() {
-        return mHasPerformedLongPress;
+
+    /**
+     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
+     * pressed.
+     *
+     * @param event The event to check.
+     * @return Whether a stylus button press occurred.
+     */
+    private static boolean isStylusButtonPressed(MotionEvent event) {
+        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
+                && event.isButtonPressed(MotionEvent.BUTTON_SECONDARY);
     }
 }
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index c99465c..8c4e4a0 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -16,6 +16,13 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
+
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -24,13 +31,17 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.util.ContentWriter;
 
+
+
 /**
  * Represents an item in the launcher.
  */
 public class ItemInfo {
 
+    public static final boolean DEBUG = true;
     public static final int NO_ID = -1;
 
     /**
@@ -190,6 +201,7 @@
         return "id=" + id
                 + " type=" + LauncherSettings.Favorites.itemTypeToString(itemType)
                 + " container=" + LauncherSettings.Favorites.containerToString((int)container)
+                + " targetComponent=" + getTargetComponent()
                 + " screen=" + screenId
                 + " cell(" + cellX + "," + cellY + ")"
                 + " span(" + spanX + "," + spanY + ")"
@@ -221,4 +233,70 @@
         return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
                 || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
     }
+
+    /**
+     * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
+     */
+    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
+    }
+
+    /**
+     * Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
+     */
+    public LauncherAtom.ItemInfo buildProto(Intent intent, FolderInfo fInfo) {
+
+        LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+        itemBuilder.setIsWork(user != Process.myUserHandle());
+        ComponentName cn = getTargetComponent();
+        switch (itemType) {
+            case ITEM_TYPE_APPLICATION:
+                itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
+                        .setComponentName(cn.flattenToShortString())
+                        .setPackageName(cn.getPackageName()));
+                break;
+            case ITEM_TYPE_DEEP_SHORTCUT:
+            case ITEM_TYPE_SHORTCUT:
+                itemBuilder.setShortcut(LauncherAtom.Shortcut.newBuilder()
+                        .setShortcutName(cn.flattenToShortString()));
+                break;
+            case ITEM_TYPE_APPWIDGET:
+                setItemBuilder(itemBuilder);
+                break;
+            default:
+                break;
+
+        }
+        if (fInfo != null) {
+            LauncherAtom.FolderContainer.Builder folderBuilder =
+                    LauncherAtom.FolderContainer.newBuilder();
+            folderBuilder.setGridX(cellX).setGridY(cellY).setPageIndex(screenId);
+
+            switch (fInfo.container) {
+                case CONTAINER_HOTSEAT:
+                    folderBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
+                            .setIndex(fInfo.screenId));
+                    break;
+                case CONTAINER_DESKTOP:
+                    folderBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
+                            .setPageIndex(fInfo.screenId)
+                            .setGridX(fInfo.cellX).setGridY(fInfo.cellY));
+                    break;
+            }
+            itemBuilder.setFolder(folderBuilder);
+        } else {
+            switch (container) {
+                case CONTAINER_HOTSEAT:
+                    itemBuilder.setHotseat(LauncherAtom.HotseatContainer.newBuilder()
+                            .setIndex(screenId));
+                    break;
+                case CONTAINER_DESKTOP:
+                    itemBuilder.setWorkspace(LauncherAtom.WorkspaceContainer.newBuilder()
+                            .setGridX(cellX)
+                            .setGridY(cellY)
+                            .setPageIndex(screenId));
+                    break;
+            }
+        }
+        return itemBuilder.build();
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c00a679..5b9f676 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -271,7 +271,6 @@
 
     // UI and state for the overview panel
     private View mOverviewPanel;
-    private View mActionsView;
 
     @Thunk
     boolean mWorkspaceLoading = true;
@@ -1166,7 +1165,6 @@
         mWorkspace = mDragLayer.findViewById(R.id.workspace);
         mWorkspace.initParentViews(mDragLayer);
         mOverviewPanel = findViewById(R.id.overview_panel);
-        mActionsView = findViewById(R.id.overview_actions_view);
         mHotseat = findViewById(R.id.hotseat);
 
         mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
@@ -1413,10 +1411,6 @@
         return (T) mOverviewPanel;
     }
 
-    public View getActionsView() {
-        return mActionsView;
-    }
-
     public DropTargetBar getDropTargetBar() {
         return mDropTargetBar;
     }
diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java
index b824301..3a478dd 100644
--- a/src/com/android/launcher3/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java
@@ -21,6 +21,7 @@
 import android.content.Intent;
 import android.os.Process;
 
+import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.util.ContentWriter;
 
@@ -162,7 +163,9 @@
 
     @Override
     protected String dumpProperties() {
-        return super.dumpProperties() + " appWidgetId=" + appWidgetId;
+        return super.dumpProperties()
+                + " providerName=" + providerName
+                + " appWidgetId=" + appWidgetId;
     }
 
     public final boolean isWidgetIdAllocated() {
@@ -182,4 +185,13 @@
     public final boolean hasOptionFlag(int option) {
         return (options & option) != 0;
     }
+
+    @Override
+    public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
+        builder.setWidget(LauncherAtom.Widget.newBuilder()
+                .setSpanX(spanX)
+                .setSpanY(spanY)
+                .setComponentName(providerName.toString())
+                .setPackageName(providerName.getPackageName()));
+    }
 }
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 6ee82cd..504666a 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -73,7 +73,7 @@
     public static final int ALL_APPS_HEADER_EXTRA = 1 << 3; // e.g. app predictions
     public static final int ALL_APPS_CONTENT = 1 << 4;
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 5;
-    public static final int RECENTS_CLEAR_ALL_BUTTON = 1 << 6;
+    public static final int OVERVIEW_BUTTONS = 1 << 6;
 
     /** Mask of all the items that are contained in the apps view. */
     public static final int APPS_VIEW_ITEM_MASK =
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index e38631d..7d7739e 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -50,6 +50,8 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -133,6 +135,7 @@
     protected int mActivePointerId = INVALID_POINTER;
 
     protected boolean mIsPageInTransition = false;
+    private Runnable mOnPageTransitionEndCallback;
 
     protected float mSpringOverScroll;
 
@@ -391,6 +394,22 @@
         AccessibilityManagerCompat.sendScrollFinishedEventToTest(getContext());
         AccessibilityManagerCompat.sendCustomAccessibilityEvent(getPageAt(mCurrentPage),
                 AccessibilityEvent.TYPE_VIEW_FOCUSED, null);
+        if (mOnPageTransitionEndCallback != null) {
+            mOnPageTransitionEndCallback.run();
+            mOnPageTransitionEndCallback = null;
+        }
+    }
+
+    /**
+     * Sets a callback to run once when the scrolling finishes. If there is currently
+     * no page in transition, then the callback is called immediately.
+     */
+    public void setOnPageTransitionEndCallback(@Nullable Runnable callback) {
+        if (mIsPageInTransition || callback == null) {
+            mOnPageTransitionEndCallback = callback;
+        } else {
+            callback.run();
+        }
     }
 
     protected int getUnboundedScroll() {
diff --git a/src/com/android/launcher3/SimpleOnStylusPressListener.java b/src/com/android/launcher3/SimpleOnStylusPressListener.java
deleted file mode 100644
index 6b97dce..0000000
--- a/src/com/android/launcher3/SimpleOnStylusPressListener.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.android.launcher3;
-
-import android.view.MotionEvent;
-import android.view.View;
-
-import com.android.launcher3.StylusEventHelper.StylusButtonListener;
-
-/**
- * Simple listener that performs a long click on the view after a stylus button press.
- */
-public class SimpleOnStylusPressListener implements StylusButtonListener {
-    private View mView;
-
-    public SimpleOnStylusPressListener(View view) {
-        mView = view;
-    }
-
-    public boolean onPressed(MotionEvent event) {
-        return mView.isLongClickable() && mView.performLongClick();
-    }
-
-    public boolean onReleased(MotionEvent event) {
-        return false;
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/StylusEventHelper.java b/src/com/android/launcher3/StylusEventHelper.java
deleted file mode 100644
index d5fc0fa..0000000
--- a/src/com/android/launcher3/StylusEventHelper.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.android.launcher3;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-/**
- * Helper for identifying when a stylus touches a view while the primary stylus button is pressed.
- * This can occur in {@value MotionEvent#ACTION_DOWN} or {@value MotionEvent#ACTION_MOVE}.
- */
-public class StylusEventHelper {
-
-    /**
-     * Implement this interface to receive callbacks for a stylus button press and release.
-     */
-    public interface StylusButtonListener {
-        /**
-         * Called when the stylus button is pressed.
-         *
-         * @param event The MotionEvent that the button press occurred for.
-         * @return Whether the event was handled.
-         */
-        public boolean onPressed(MotionEvent event);
-
-        /**
-         * Called when the stylus button is released after a button press. This is also called if
-         * the event is canceled or the stylus is lifted off the screen.
-         *
-         * @param event The MotionEvent the button release occurred for.
-         * @return Whether the event was handled.
-         */
-        public boolean onReleased(MotionEvent event);
-    }
-
-    private boolean mIsButtonPressed;
-    private View mView;
-    private StylusButtonListener mListener;
-    private final float mSlop;
-
-    /**
-     * Constructs a helper for listening to stylus button presses and releases. Ensure that {
-     * {@link #onMotionEvent(MotionEvent)} and {@link #onGenericMotionEvent(MotionEvent)} are called on
-     * the helper to correctly identify stylus events.
-     *
-     * @param listener The listener to call for stylus events.
-     * @param view Optional view associated with the touch events.
-     */
-    public StylusEventHelper(StylusButtonListener listener, View view) {
-        mListener = listener;
-        mView = view;
-        if (mView != null) {
-            mSlop = ViewConfiguration.get(mView.getContext()).getScaledTouchSlop();
-        } else {
-            mSlop = ViewConfiguration.getTouchSlop();
-        }
-    }
-
-    public boolean onMotionEvent(MotionEvent event) {
-        final boolean stylusButtonPressed = isStylusButtonPressed(event);
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mIsButtonPressed = stylusButtonPressed;
-                if (mIsButtonPressed) {
-                    return mListener.onPressed(event);
-                }
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(mView, event.getX(), event.getY(), mSlop)) {
-                    return false;
-                }
-                if (!mIsButtonPressed && stylusButtonPressed) {
-                    mIsButtonPressed = true;
-                    return mListener.onPressed(event);
-                } else if (mIsButtonPressed && !stylusButtonPressed) {
-                    mIsButtonPressed = false;
-                    return mListener.onReleased(event);
-                }
-                break;
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                if (mIsButtonPressed) {
-                    mIsButtonPressed = false;
-                    return mListener.onReleased(event);
-                }
-                break;
-        }
-        return false;
-    }
-
-    /**
-     * Whether a stylus button press is occurring.
-     */
-    public boolean inStylusButtonPressed() {
-        return mIsButtonPressed;
-    }
-
-    /**
-     * Identifies if the provided {@link MotionEvent} is a stylus with the primary stylus button
-     * pressed.
-     *
-     * @param event The event to check.
-     * @return Whether a stylus button press occurred.
-     */
-    private static boolean isStylusButtonPressed(MotionEvent event) {
-        return event.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
-                && ((event.getButtonState() & MotionEvent.BUTTON_SECONDARY)
-                        == MotionEvent.BUTTON_SECONDARY);
-    }
-}
\ No newline at end of file
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index ee9c099..8bc0242 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -991,7 +991,6 @@
             if (!mOverlayShown) {
                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.LEFT, ContainerType.WORKSPACE, 0);
-                mLauncher.getStatsLogManager().logSwipeOnContainer(true, 0);
             }
             mOverlayShown = true;
             // Not announcing the overlay page for accessibility since it announces itself.
@@ -1001,7 +1000,6 @@
                 if (!ued.isPreviousHomeGesture()) {
                     mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.SWIPE,
                         Action.Direction.RIGHT, ContainerType.WORKSPACE, -1);
-                    mLauncher.getStatsLogManager().logSwipeOnContainer(false, -1);
                 }
             } else if (Float.compare(mOverlayTranslation, 0f) != 0) {
                 // When arriving to 0 overscroll from non-zero overscroll, announce page for
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 6f7f8e6..24c846c 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -146,11 +146,21 @@
             }
         }
 
+        if (!fromKeyboard && !itemSupportsLongClick(host, item)) {
+            info.setLongClickable(false);
+            info.removeAction(AccessibilityAction.ACTION_LONG_CLICK);
+        }
+
         if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
             info.addAction(mActions.get(ADD_TO_WORKSPACE));
         }
     }
 
+    private boolean itemSupportsLongClick(View host, ItemInfo info) {
+        return new CustomActionsPopup(mLauncher, host).canShow()
+                || ShortcutUtil.supportsShortcuts(info);
+    }
+
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
         if (item instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
@@ -171,18 +181,18 @@
 
     public boolean performAction(final View host, final ItemInfo item, int action) {
         if (action == ACTION_LONG_CLICK) {
-            if (ShortcutUtil.isDeepShortcut(item)) {
-                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
-                if (popup.canShow()) {
-                    popup.show();
-                    return true;
-                }
-            } else if (host instanceof BubbleTextView) {
+            if (ShortcutUtil.supportsShortcuts(item)) {
                 // Long press should be consumed for workspace items, and it should invoke the
                 // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
                 // standard long press path does.
                 PopupContainerWithArrow.showForIcon((BubbleTextView) host);
                 return true;
+            } else {
+                CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+                if (popup.canShow()) {
+                    popup.show();
+                    return true;
+                }
             }
         }
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index f0d18ae..8251d68 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -31,7 +31,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -52,8 +51,6 @@
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.OnAlarmListener;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
@@ -87,7 +84,6 @@
     private FolderInfo mInfo;
 
     private CheckLongPressHelper mLongPressHelper;
-    private StylusEventHelper mStylusEventHelper;
 
     static final int DROP_IN_ANIMATION_DURATION = 400;
 
@@ -110,8 +106,6 @@
 
     boolean mAnimating = false;
 
-    private float mSlop;
-
     private Alarm mOpenAlarm = new Alarm();
 
     private boolean mForceHideDot;
@@ -149,9 +143,7 @@
 
     private void init() {
         mLongPressHelper = new CheckLongPressHelper(this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         mPreviewItemManager = new PreviewItemManager(this);
         mDotParams = new DotRenderer.DrawParams();
     }
@@ -663,29 +655,10 @@
     public boolean onTouchEvent(MotionEvent event) {
         // Call the superclass onTouchEvent first, because sometimes it changes the state to
         // isPressed() on an ACTION_UP
-        boolean result = super.onTouchEvent(event);
-
-        // Check for a stylus button press, if it occurs cancel any long press checks.
-        if (mStylusEventHelper.onMotionEvent(event)) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        switch (event.getAction()) {
-            case MotionEvent.ACTION_DOWN:
-                mLongPressHelper.postCheckForLongPress();
-                break;
-            case MotionEvent.ACTION_CANCEL:
-            case MotionEvent.ACTION_UP:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
-        return result;
+        super.onTouchEvent(event);
+        mLongPressHelper.onTouchEvent(event);
+        // Keep receiving the rest of the events
+        return true;
     }
 
     @Override
diff --git a/src/com/android/launcher3/logging/LauncherUiEvent.java b/src/com/android/launcher3/logging/LauncherUiEvent.java
new file mode 100644
index 0000000..4507ff7
--- /dev/null
+++ b/src/com/android/launcher3/logging/LauncherUiEvent.java
@@ -0,0 +1,30 @@
+/*
+ * 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.logging;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(SOURCE)
+@Target(FIELD)
+public @interface LauncherUiEvent {
+    /** An explanation, suitable for Android analysts, of the UI event that this log represents. */
+    String doc();
+}
+
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 9dfd7ab..2829951 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -16,23 +16,44 @@
 package com.android.launcher3.logging;
 
 import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.view.View;
-
-import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.logger.LauncherAtom.ItemInfo;
 import com.android.launcher3.logging.StatsLogUtils.LogStateProvider;
-import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.ResourceBasedOverride;
 
 /**
- * Handles the user event logging in Q.
+ * Handles the user event logging in R+.
  */
 public class StatsLogManager implements ResourceBasedOverride {
 
+    interface EventEnum {
+        int getId();
+    }
+
+    public enum LauncherEvent implements EventEnum {
+        @LauncherUiEvent(doc = "App launched from workspace, hotseat or folder in launcher")
+        APP_LAUNCH_TAP(1),
+        @LauncherUiEvent(doc = "Task launched from overview using TAP")
+        TASK_LAUNCH_TAP(2),
+        @LauncherUiEvent(doc = "Task launched from overview using SWIPE DOWN")
+        TASK_LAUNCH_SWIPE_DOWN(2),
+        @LauncherUiEvent(doc = "TASK dismissed from overview using SWIPE UP")
+        TASK_DISMISS_SWIPE_UP(3);
+        // ADD MORE
+
+        private final int mId;
+        LauncherEvent(int id) {
+            mId = id;
+        }
+        public int getId() {
+            return mId;
+        }
+    }
+
     protected LogStateProvider mStateProvider;
+
     public static StatsLogManager newInstance(Context context, LogStateProvider stateProvider) {
         StatsLogManager mgr = Overrides.getObject(StatsLogManager.class,
                 context.getApplicationContext(), R.string.stats_log_manager_class);
@@ -42,11 +63,14 @@
     }
 
     /**
-     * Logs app launches
+     * Logs an event and accompanying {@link ItemInfo}
      */
-    public void logAppLaunch(View v, Intent intent, @Nullable UserHandle userHandle) { }
-    public void logTaskLaunch(View v, ComponentKey key) { }
-    public void logTaskDismiss(View v, ComponentKey key) { }
-    public void logSwipeOnContainer(boolean isSwipingToLeft, int pageId) { }
+    public void log(LauncherEvent eventId, LauncherAtom.ItemInfo itemInfo) { }
+
+    /**
+     * Logs snapshot, or impression of the current workspace.
+     */
+    public void logSnapshot() { }
+
     public void verify() {}     // TODO: should move into robo tests
 }
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 2311dcc..695d2a6 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -35,6 +35,8 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 
+import androidx.annotation.VisibleForTesting;
+
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
@@ -150,8 +152,10 @@
         }
     }
 
+    @VisibleForTesting
     public WorkspaceItemInfo loadSimpleWorkspaceItem() {
         final WorkspaceItemInfo info = new WorkspaceItemInfo();
+        info.intent = new Intent();
         // Non-app shortcuts are only supported for current user.
         info.user = user;
         info.itemType = itemType;
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index e70b570..9f20df6 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -97,4 +97,5 @@
     public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
     public static final String APP_NOT_DISABLED = "b/139891609";
     public static final String NO_SCROLL_END_WIDGETS = "b/152354290";
+    public static final String NO_START_FROM_RECENTS = "b/152658211";
 }
diff --git a/src/com/android/launcher3/util/OverScroller.java b/src/com/android/launcher3/util/OverScroller.java
index 3c398b8..34efb12 100644
--- a/src/com/android/launcher3/util/OverScroller.java
+++ b/src/com/android/launcher3/util/OverScroller.java
@@ -165,6 +165,9 @@
     /**
      * Returns how long the scroll event will take, in milliseconds.
      *
+     * Note that if mScroller.mState == SPRING, this duration is ignored, so can only
+     * serve as an estimate for how long the spring-controlled scroll will take.
+     *
      * @return The duration of the scroll in milliseconds.
      */
     public final int getDuration() {
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 868c91d..2fc3eaf 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -33,6 +33,7 @@
 import android.graphics.RectF;
 import android.os.Build;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Property;
 import android.view.MotionEvent;
 import android.view.View;
@@ -48,6 +49,7 @@
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -273,6 +275,9 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "BaseDragLayer: " + ev);
+        }
         switch (ev.getAction()) {
             case ACTION_DOWN: {
                 mTouchDispatchState |= TOUCH_DISPATCHING_VIEW;
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index c1310e3..78acc34 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -27,7 +27,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -41,8 +40,6 @@
 import com.android.launcher3.LauncherAppWidgetInfo;
 import com.android.launcher3.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DraggableView;
@@ -66,14 +63,11 @@
     protected final LayoutInflater mInflater;
 
     private final CheckLongPressHelper mLongPressHelper;
-    private final StylusEventHelper mStylusEventHelper;
     protected final Launcher mLauncher;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mReinflateOnConfigChange;
 
-    private float mSlop;
-
     private boolean mIsScrollable;
     private boolean mIsAttachedToWindow;
     private boolean mIsAutoAdvanceRegistered;
@@ -93,7 +87,6 @@
         super(context);
         mLauncher = Launcher.getLauncher(context);
         mLongPressHelper = new CheckLongPressHelper(this, this);
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
         mInflater = LayoutInflater.from(context);
         setAccessibilityDelegate(mLauncher.getAccessibilityDelegate());
         setBackgroundResource(R.drawable.widget_internal_focus_bg);
@@ -157,68 +150,19 @@
     }
 
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        // Just in case the previous long press hasn't been cleared, we make sure to start fresh
-        // on touch down.
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            mLongPressHelper.cancelLongPress();
-        }
-
-        // Consume any touch events for ourselves after longpress is triggered
-        if (mLongPressHelper.hasPerformedLongPress()) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        // Watch for longpress or stylus button press events at this level to
-        // make sure users can always pick up this widget
-        if (mStylusEventHelper.onMotionEvent(ev)) {
-            mLongPressHelper.cancelLongPress();
-            return true;
-        }
-
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_DOWN: {
-                DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
-
-                if (mIsScrollable) {
-                     dragLayer.requestDisallowInterceptTouchEvent(true);
-                }
-                if (!mStylusEventHelper.inStylusButtonPressed()) {
-                    mLongPressHelper.postCheckForLongPress();
-                }
-                dragLayer.setTouchCompleteListener(this);
-                break;
+            DragLayer dragLayer = Launcher.getLauncher(getContext()).getDragLayer();
+            if (mIsScrollable) {
+                dragLayer.requestDisallowInterceptTouchEvent(true);
             }
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
+            dragLayer.setTouchCompleteListener(this);
         }
-
-        // Otherwise continue letting touch events fall through to children
-        return false;
+        mLongPressHelper.onTouchEvent(ev);
+        return mLongPressHelper.hasPerformedLongPress();
     }
 
     public boolean onTouchEvent(MotionEvent ev) {
-        // If the widget does not handle touch, then cancel
-        // long press when we release the touch
-        switch (ev.getAction()) {
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL:
-                mLongPressHelper.cancelLongPress();
-                break;
-            case MotionEvent.ACTION_MOVE:
-                if (!Utilities.pointInView(this, ev.getX(), ev.getY(), mSlop)) {
-                    mLongPressHelper.cancelLongPress();
-                }
-                break;
-        }
+        mLongPressHelper.onTouchEvent(ev);
         // We want to keep receiving though events to be able to cancel long press on ACTION_UP
         return true;
     }
@@ -226,7 +170,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
 
         mIsAttachedToWindow = true;
         checkIfAutoAdvance();
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f055adf..4a0b4ef 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -31,10 +31,9 @@
 import android.widget.TextView;
 
 import com.android.launcher3.BaseActivity;
+import com.android.launcher3.CheckLongPressHelper;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.SimpleOnStylusPressListener;
-import com.android.launcher3.StylusEventHelper;
 import com.android.launcher3.WidgetPreviewLoader;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.model.WidgetItem;
@@ -71,7 +70,6 @@
     protected WidgetItem mItem;
 
     private WidgetPreviewLoader mWidgetPreviewLoader;
-    private StylusEventHelper mStylusEventHelper;
 
     protected CancellationSignal mActiveRequest;
     private boolean mAnimatePreview = true;
@@ -80,7 +78,8 @@
     private Bitmap mDeferredBitmap;
 
     protected final BaseActivity mActivity;
-    protected DeviceProfile mDeviceProfile;
+    protected final DeviceProfile mDeviceProfile;
+    private final CheckLongPressHelper mLongPressHelper;
 
     public WidgetCell(Context context) {
         this(context, null);
@@ -95,8 +94,9 @@
 
         mActivity = BaseActivity.fromContext(context);
         mDeviceProfile = mActivity.getDeviceProfile();
-        mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
+        mLongPressHelper = new CheckLongPressHelper(this);
 
+        mLongPressHelper.setLongPressTimeoutFactor(1);
         setContainerWidth();
         setWillNotDraw(false);
         setClipToPadding(false);
@@ -210,11 +210,15 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
-        boolean handled = super.onTouchEvent(ev);
-        if (mStylusEventHelper.onMotionEvent(ev)) {
-            return true;
-        }
-        return handled;
+        super.onTouchEvent(ev);
+        mLongPressHelper.onTouchEvent(ev);
+        return true;
+    }
+
+    @Override
+    public void cancelLongPress() {
+        super.cancelLongPress();
+        mLongPressHelper.cancelLongPress();
     }
 
     /**
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index b07a4f4..aaebedd 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.Pair;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -38,8 +39,10 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
 
@@ -68,6 +71,14 @@
 
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.NO_SCROLL_END_WIDGETS, "WidgetsFullSheet: " + ev);
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
     public WidgetsFullSheet(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fbf8508..1861ad1 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -334,15 +334,21 @@
 
     private String getSystemAnomalyMessage() {
         try {
+            final StringBuilder sb = new StringBuilder();
+
             UiObject2 object = mDevice.findObject(By.res("android", "alertTitle"));
             if (object != null) {
-                return "System alert popup is visible: " + object.getText();
+                sb.append("TITLE: ").append(object.getText());
             }
 
             object = mDevice.findObject(By.res("android", "message"));
             if (object != null) {
-                return "Message popup by " + object.getApplicationPackage() + " is visible: "
-                        + object.getText();
+                sb.append(" PACKAGE: ").append(object.getApplicationPackage())
+                        .append(" MESSAGE: ").append(object.getText());
+            }
+
+            if (sb.length() != 0) {
+                return "System alert popup is visible: " + sb;
             }
 
             if (hasSystemUiObject("keyguard_status_view")) return "Phone is locked";
diff --git a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
index 0fc88ee..49901ea 100644
--- a/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
+++ b/tests/tapl/com/android/launcher3/tapl/LogEventChecker.java
@@ -149,23 +149,24 @@
         finishSync(waitForExpectedCountMs);
 
         final StringBuilder sb = new StringBuilder();
+        boolean hasMismatches = false;
         for (Map.Entry<String, List<Pattern>> expectedEvents : mExpectedEvents.entrySet()) {
             String sequence = expectedEvents.getKey();
 
             List<String> actual = new ArrayList<>(mEvents.getNonNull(sequence));
             final int mismatchPosition = getMismatchPosition(expectedEvents.getValue(), actual);
-            if (mismatchPosition != -1) {
-                formatSequenceWithMismatch(
-                        sb,
-                        sequence,
-                        expectedEvents.getValue(),
-                        actual,
-                        mismatchPosition);
-            }
+            hasMismatches = hasMismatches || mismatchPosition != -1;
+            formatSequenceWithMismatch(
+                    sb,
+                    sequence,
+                    expectedEvents.getValue(),
+                    actual,
+                    mismatchPosition);
         }
         // Check for unexpected event sequences in the actual data.
         for (String actualNamedSequence : mEvents.keySet()) {
             if (!mExpectedEvents.containsKey(actualNamedSequence)) {
+                hasMismatches = true;
                 formatSequenceWithMismatch(
                         sb,
                         actualNamedSequence,
@@ -175,7 +176,7 @@
             }
         }
 
-        return sb.length() != 0 ? "mismatching events: " + sb.toString() : null;
+        return hasMismatches ? "mismatching events: " + sb.toString() : null;
     }
 
     // If the list of actual events matches the list of expected events, returns -1, otherwise
@@ -199,10 +200,11 @@
             List<Pattern> expected,
             List<String> actualEvents,
             int mismatchPosition) {
-        sb.append("\n>> Sequence " + sequenceName);
-        sb.append("\n  Expected:");
+        sb.append("\n>> SEQUENCE " + sequenceName + " - "
+                + (mismatchPosition == -1 ? "MATCH" : "MISMATCH"));
+        sb.append("\n  EXPECTED:");
         formatEventListWithMismatch(sb, expected, mismatchPosition);
-        sb.append("\n  Actual:");
+        sb.append("\n  ACTUAL:");
         formatEventListWithMismatch(sb, actualEvents, mismatchPosition);
     }