Merge "Re-enable orientation sensor for Overview animation" into ub-launcher3-master
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/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index 20b1485..363840a 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -31,9 +31,6 @@
     <dimen name="all_apps_label_top_padding">16dp</dimen>
     <dimen name="all_apps_label_bottom_padding">8dp</dimen>
     <dimen name="all_apps_label_text_size">14sp</dimen>
-    <dimen name="all_apps_tip_bottom_margin">8dp</dimen>
-    <!-- The size of corner radius of the arrow in the arrow toast. -->
-    <dimen name="arrow_toast_corner_radius">2dp</dimen>
 
     <!-- Minimum distance to swipe to trigger accessibility gesture -->
     <dimen name="accessibility_gesture_min_swipe_distance">80dp</dimen>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index b3bb850..079a738 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -24,13 +24,13 @@
 import android.os.UserManager;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderView;
+import com.android.launcher3.views.ArrowTipView;
 import com.android.systemui.shared.system.LauncherEventUtil;
 
 /**
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 5b01185..773c6c8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -26,7 +26,6 @@
 
 import androidx.core.app.NotificationCompat;
 
-import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.Hotseat;
@@ -43,6 +42,7 @@
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ArrowTipView;
 import com.android.launcher3.views.Snackbar;
 
 import java.util.ArrayDeque;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 496a3d8..61fe6cb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -444,9 +444,12 @@
             GestureState newGestureState;
 
             if (mDeviceState.isInSwipeUpTouchRegion(event)) {
+                // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
+                // onConsumerInactive and wipe the previous gesture state
+                GestureState prevGestureState = new GestureState(mGestureState);
                 newGestureState = createGestureState();
                 mConsumer.onConsumerAboutToBeSwitched();
-                mConsumer = newConsumer(mGestureState, newGestureState, event);
+                mConsumer = newConsumer(prevGestureState, newGestureState, event);
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer", mConsumer.getType());
                 mUncheckedConsumer = mConsumer;
@@ -686,7 +689,7 @@
      * To be called by the consumer when it's no longer active.
      */
     private void onConsumerInactive(InputConsumer caller) {
-        if (mConsumer == caller) {
+        if (mConsumer != null && mConsumer.isInConsumerHierarchy(caller)) {
             mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
             mGestureState = new GestureState();
         }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 71465eb..bcc9707 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -30,6 +30,11 @@
     }
 
     @Override
+    public boolean isInConsumerHierarchy(InputConsumer candidate) {
+        return this == candidate || mDelegate.isInConsumerHierarchy(candidate);
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
     }
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 4a36f54..c73b3ee 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;
@@ -1241,13 +1243,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());
         }
     }
 
@@ -1342,7 +1344,7 @@
             private void onEnd(EndState endState) {
                 if (endState.isSuccess) {
                     if (shouldRemoveTask) {
-                        removeTask(taskView.getTask(), draggedIndex, endState);
+                        removeTask(taskView, draggedIndex, endState);
                     }
 
                     int pageToSnapTo = mCurrentPage;
@@ -1791,6 +1793,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..7010f9a 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();
@@ -812,8 +826,8 @@
         super.onInitializeAccessibilityNodeInfo(info);
 
         info.addAction(
-                new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close_task,
-                        getContext().getText(R.string.accessibility_close_task)));
+                new AccessibilityNodeInfo.AccessibilityAction(R.string.accessibility_close,
+                        getContext().getText(R.string.accessibility_close)));
 
         final Context context = getContext();
         for (SystemShortcut s : TaskOverlayFactory.getEnabledShortcuts(this)) {
@@ -837,7 +851,7 @@
 
     @Override
     public boolean performAccessibilityAction(int action, Bundle arguments) {
-        if (action == R.string.accessibility_close_task) {
+        if (action == R.string.accessibility_close) {
             getRecentsView().dismissTask(this, true /*animateTaskView*/,
                     true /*removeTask*/);
             return true;
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 40d7c7a..9ef38c0 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -32,9 +32,6 @@
     <!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
     <string name="recents_empty_message">No recent items</string>
 
-    <!-- Content description for the recent apps's accessibility option that closes it. [CHAR LIMIT=NONE] -->
-    <string name="accessibility_close_task">Close</string>
-
     <!-- Content description for the recent apps's accessibility option that opens its usage settings. [CHAR LIMIT=NONE] -->
     <string name="accessibility_app_usage_settings">App usage settings</string>
 
@@ -116,7 +113,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/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index 631df4c..5118906 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -130,6 +130,17 @@
         mGestureId = gestureId;
     }
 
+    public GestureState(GestureState other) {
+        mHomeIntent = other.mHomeIntent;
+        mOverviewIntent = other.mOverviewIntent;
+        mActivityInterface = other.mActivityInterface;
+        mStateCallback = other.mStateCallback;
+        mGestureId = other.mGestureId;
+        mRunningTask = other.mRunningTask;
+        mEndTarget = other.mEndTarget;
+        mFinishingRecentsAnimationTaskId = other.mFinishingRecentsAnimationTaskId;
+    }
+
     public GestureState() {
         // Do nothing, only used for initializing the gesture state prior to user unlock
         mHomeIntent = new Intent();
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 8efaeb9..818d836 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -70,6 +70,13 @@
     }
 
     /**
+     * Returns true if the given input consumer is in the hierarchy of this input consumer.
+     */
+    default boolean isInConsumerHierarchy(InputConsumer candidate) {
+        return this == candidate;
+    }
+
+    /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
      * Consumers should update the state accordingly here before the state is passed to the new
      * consumer.
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/quickstep/recents_ui_overrides/res/drawable/arrow_toast_rounded_background.xml b/res/drawable/arrow_toast_rounded_background.xml
similarity index 100%
rename from quickstep/recents_ui_overrides/res/drawable/arrow_toast_rounded_background.xml
rename to res/drawable/arrow_toast_rounded_background.xml
diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/res/layout/arrow_toast.xml
similarity index 99%
rename from quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
rename to res/layout/arrow_toast.xml
index 980bb5a..087e45a 100644
--- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
+++ b/res/layout/arrow_toast.xml
@@ -50,7 +50,7 @@
             android:src="@drawable/ic_remove_no_shadow"
             android:tint="@android:color/white"
             android:background="?android:attr/selectableItemBackgroundBorderless"
-            android:contentDescription="@string/accessibility_close_task"/>
+            android:contentDescription="@string/accessibility_close"/>
     </LinearLayout>
 
     <View
diff --git a/res/layout/search_container_all_apps.xml b/res/layout/search_container_all_apps.xml
index fd9cb60..e1646ba 100644
--- a/res/layout/search_container_all_apps.xml
+++ b/res/layout/search_container_all_apps.xml
@@ -34,5 +34,4 @@
     android:singleLine="true"
     android:textColor="?android:attr/textColorSecondary"
     android:textColorHint="@drawable/all_apps_search_hint"
-    android:textSize="16sp"
-    android:translationY="24dp" />
\ No newline at end of file
+    android:textSize="16sp" />
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 871651d..271511e 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,10 @@
     <dimen name="all_apps_tabs_side_padding">12dp</dimen>
     <dimen name="all_apps_divider_height">1dp</dimen>
 
+    <dimen name="all_apps_tip_bottom_margin">8dp</dimen>
+    <!-- The size of corner radius of the arrow in the arrow toast. -->
+    <dimen name="arrow_toast_corner_radius">2dp</dimen>
+
     <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
 
 <!-- Search bar in All Apps -->
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b1077be..ac04262 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -316,6 +316,9 @@
     <!-- Accessibility action to dismiss a notification in the shortcuts menu for an icon. [CHAR_LIMIT=30] -->
     <string name="action_dismiss_notification">Dismiss</string>
 
+    <!-- Content description for arrow tip close button. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_close">Close</string>
+
     <!-- Accessibility confirmation for notification being dismissed. -->
     <string name="notification_dismissed">Notification dismissed</string>
 
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/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/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/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..f45c0b1 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 PopupContainerWithArrow.canShow(host, info)
+                || new CustomActionsPopup(mLauncher, host).canShow();
+    }
+
     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 (PopupContainerWithArrow.canShow(host, 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/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index f935e4d..05db18e 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -17,8 +17,6 @@
 
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Rect;
@@ -27,7 +25,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.AttributeSet;
-import android.view.MotionEvent;
 import android.widget.Switch;
 
 import com.android.launcher3.Insettable;
@@ -35,6 +32,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.views.ArrowTipView;
 
 import java.lang.ref.WeakReference;
 
@@ -43,27 +41,21 @@
  */
 public class WorkModeSwitch extends Switch implements Insettable {
 
-    private Rect mInsets = new Rect();
-    protected ObjectAnimator mOpenCloseAnimator;
+    private static final int WORK_TIP_THRESHOLD = 2;
+    public static final String KEY_WORK_TIP_COUNTER = "worked_tip_counter";
 
+    private Rect mInsets = new Rect();
 
     public WorkModeSwitch(Context context) {
         super(context);
-        init();
     }
 
     public WorkModeSwitch(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
     }
 
     public WorkModeSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
-        init();
-    }
-
-    private void init() {
-        mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
     }
 
     @Override
@@ -73,6 +65,9 @@
 
     @Override
     public void toggle() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        // don't show tip if user uses toggle
+        launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, -1).apply();
         trySetQuietModeEnabledToAllProfilesAsync(isChecked());
     }
 
@@ -95,11 +90,6 @@
         this.setVisibility(shouldShowWorkSwitch() ? VISIBLE : GONE);
     }
 
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return ev.getActionMasked() == MotionEvent.ACTION_MOVE || super.onTouchEvent(ev);
-    }
-
     private void trySetQuietModeEnabledToAllProfilesAsync(boolean enabled) {
         new SetQuietModeEnabledAsyncTask(enabled, new WeakReference<>(this)).execute();
     }
@@ -117,9 +107,15 @@
      */
     public void setWorkTabVisible(boolean workTabVisible) {
         if (!shouldShowWorkSwitch()) return;
-
-        mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
-        mOpenCloseAnimator.start();
+        clearAnimation();
+        if (workTabVisible) {
+            setVisibility(VISIBLE);
+            setAlpha(0);
+            animate().alpha(1).start();
+            showTipifNeeded();
+        } else {
+            animate().alpha(0).withEndAction(() -> this.setVisibility(GONE)).start();
+        }
     }
 
     private static final class SetQuietModeEnabledAsyncTask
@@ -179,4 +175,17 @@
                 || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
                 == PackageManager.PERMISSION_GRANTED);
     }
+
+    /**
+     * Shows a work tip on the Nth work tab open
+     */
+    public void showTipifNeeded() {
+        Launcher launcher = Launcher.getLauncher(getContext());
+        int tipCounter = launcher.getSharedPrefs().getInt(KEY_WORK_TIP_COUNTER, WORK_TIP_THRESHOLD);
+        if (tipCounter < 0) return;
+        if (tipCounter == 0) {
+            new ArrowTipView(launcher).show(launcher.getString(R.string.work_switch_tip), getTop());
+        }
+        launcher.getSharedPrefs().edit().putInt(KEY_WORK_TIP_COUNTER, tipCounter - 1).apply();
+    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index d497c3a..9e3a862 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -62,9 +62,8 @@
     private AlphabeticalAppsList mApps;
     private AllAppsContainerView mAppsView;
 
-    // This value was used to position the QSB. We store it here for translationY animations.
-    private final float mFixedTranslationY;
-    private final float mMarginTopAdjusting;
+    // The amount of pixels to shift down and overlap with the rest of the content.
+    private final int mContentOverlap;
 
     public AppsSearchContainerLayout(Context context) {
         this(context, null);
@@ -82,11 +81,10 @@
 
         mSearchQueryBuilder = new SpannableStringBuilder();
         Selection.setSelection(mSearchQueryBuilder, 0);
-
-        mFixedTranslationY = getTranslationY();
-        mMarginTopAdjusting = mFixedTranslationY - getPaddingTop();
-
         setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
+
+        mContentOverlap =
+                getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height) / 2;
     }
 
     @Override
@@ -128,6 +126,8 @@
         int expectedLeft = parent.getPaddingLeft() + (availableWidth - myWidth) / 2;
         int shift = expectedLeft - left;
         setTranslationX(shift);
+
+        offsetTopAndBottom(mContentOverlap);
     }
 
     @Override
@@ -196,7 +196,7 @@
     @Override
     public void setInsets(Rect insets) {
         MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
-        mlp.topMargin = Math.round(Math.max(-mFixedTranslationY, insets.top - mMarginTopAdjusting));
+        mlp.topMargin = insets.top;
         requestLayout();
     }
 
@@ -205,9 +205,7 @@
         if (mLauncher.getDeviceProfile().isVerticalBarLayout()) {
             return 0;
         } else {
-            int topMargin = Math.round(Math.max(
-                    -mFixedTranslationY, insets.top - mMarginTopAdjusting));
-           return insets.bottom + topMargin + mFixedTranslationY;
+            return insets.bottom + insets.top;
         }
     }
 
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/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 9bac259..406e1b2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -185,6 +185,13 @@
     }
 
     /**
+     * Returns true if we can show the container.
+     */
+    public static boolean canShow(View icon, ItemInfo item) {
+        return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item);
+    }
+
+    /**
      * Shows the notifications and deep shortcuts associated with {@param icon}.
      * @return the container if shown or null.
      */
@@ -196,7 +203,7 @@
             return null;
         }
         ItemInfo item = (ItemInfo) icon.getTag();
-        if (!ShortcutUtil.supportsShortcuts(item)) {
+        if (!canShow(icon, item)) {
             return null;
         }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index fae0fe2..d28fcf6 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.states;
 
+import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
 import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
@@ -37,7 +39,6 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
@@ -142,9 +143,12 @@
         if (setValueFromPrefs) {
             mForcedRotation = isForcedRotation;
         }
-        UI_HELPER_EXECUTOR.execute(
-                () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
-                        mForcedRotation ? 1 : 0));
+        UI_HELPER_EXECUTOR.execute(() -> {
+            if (mLauncher.checkSelfPermission(WRITE_SECURE_SETTINGS) == PERMISSION_GRANTED) {
+                Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+                            mForcedRotation ? 1 : 0);
+            }
+        });
         for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
             listener.onForcedRotationChanged(mForcedRotation);
         }
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index ba1bfa5..8537bdf 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -33,6 +33,8 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -46,6 +48,7 @@
             ItemLongClickListener::onAllAppsItemLongClick;
 
     private static boolean onWorkspaceItemLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onWorkspaceItemLongClick");
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
@@ -75,6 +78,8 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onAllAppsItemLongClick");
+        v.cancelLongPress();
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         // When we have exited all apps or are in transition, disregard long clicks
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 310d598..da631bd 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -38,6 +38,8 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -165,6 +167,7 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Workspace.longPress");
         if (mLongPressState == STATE_REQUESTED) {
             if (canHandleLongPress()) {
                 mLongPressState = STATE_PENDING_PARENT_INFORM;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
similarity index 96%
rename from quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
rename to src/com/android/launcher3/views/ArrowTipView.java
index a5ea523..60470dc 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.launcher3;
+package com.android.launcher3.views;
 
 import android.content.Context;
 import android.graphics.CornerPathEffect;
@@ -31,6 +31,9 @@
 
 import androidx.core.content.ContextCompat;
 
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.TriangleShape;
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/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 73a0615..23c2160 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -33,6 +33,8 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -92,6 +94,8 @@
 
     @Override
     public boolean onLongClick(View v) {
+        TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "Widgets.onLongClick");
+        v.cancelLongPress();
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
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/src/com/android/launcher3/ui/widget/BindWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
index a3c70ec..001a88f 100644
--- a/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/BindWidgetTest.java
@@ -275,8 +275,10 @@
     }
 
     private void verifyPendingWidgetPresent() {
+        final Widget widget = mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT);
+        if (widget == null) mLauncher.dumpViewHierarchy(); // b/152645831
         assertTrue("Pending widget is not present",
-                mLauncher.getWorkspace().tryGetPendingWidget(DEFAULT_UI_TIMEOUT) != null);
+                widget != null);
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 4a2d699..808be66 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -43,7 +43,7 @@
     AllApps(LauncherInstrumentation launcher) {
         super(launcher);
         final UiObject2 allAppsContainer = verifyActiveContainer();
-        mHeight = allAppsContainer.getVisibleBounds().height();
+        mHeight = mLauncher.getVisibleBounds(allAppsContainer).height();
         final UiObject2 appListRecycler = mLauncher.waitForObjectInContainer(allAppsContainer,
                 "apps_list_view");
         // Wait for the recycler to populate.
@@ -66,7 +66,7 @@
             LauncherInstrumentation.log("hasClickableIcon: icon not visible");
             return false;
         }
-        final Rect iconBounds = icon.getVisibleBounds();
+        final Rect iconBounds = mLauncher.getVisibleBounds(icon);
         LauncherInstrumentation.log("hasClickableIcon: icon bounds: " + iconBounds);
         if (iconBounds.height() < mIconHeight / 2) {
             LauncherInstrumentation.log("hasClickableIcon: icon has insufficient height");
@@ -86,7 +86,7 @@
 
     private boolean iconCenterInSearchBox(UiObject2 allAppsContainer, UiObject2 icon) {
         final Point iconCenter = icon.getVisibleCenter();
-        return getSearchBox(allAppsContainer).getVisibleBounds().contains(
+        return mLauncher.getVisibleBounds(getSearchBox(allAppsContainer)).contains(
                 iconCenter.x, iconCenter.y);
     }
 
@@ -125,11 +125,11 @@
                                 mLauncher.getObjectsInContainer(allAppsContainer, "icon")
                                         .stream()
                                         .filter(icon ->
-                                                icon.getVisibleBounds().bottom
+                                                        mLauncher.getVisibleBounds(icon).bottom
                                                         <= displayBottom)
                                         .collect(Collectors.toList()),
-                                searchBox.getVisibleBounds().bottom
-                                        - allAppsContainer.getVisibleBounds().top);
+                                mLauncher.getVisibleBounds(searchBox).bottom
+                                        - mLauncher.getVisibleBounds(allAppsContainer).top);
                         final int newScroll = getAllAppsScroll();
                         mLauncher.assertTrue(
                                 "Scrolled in a wrong direction in AllApps: from " + scroll + " to "
@@ -166,7 +166,8 @@
             final UiObject2 searchBox = getSearchBox(allAppsContainer);
 
             int attempts = 0;
-            final Rect margins = new Rect(0, searchBox.getVisibleBounds().bottom + 1, 0, 5);
+            final Rect margins =
+                    new Rect(0, mLauncher.getVisibleBounds(searchBox).bottom + 1, 0, 5);
 
             for (int scroll = getAllAppsScroll();
                     scroll != 0;
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 8932291..bdfd563 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -32,6 +32,7 @@
 public final class AppIcon extends Launchable {
 
     private static final Pattern START_EVENT = Pattern.compile("start:");
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
 
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
@@ -47,11 +48,16 @@
     public AppIconMenu openMenu() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             return new AppIconMenu(mLauncher, mLauncher.clickAndGet(
-                    mObject, "deep_shortcuts_container"));
+                    mObject, "deep_shortcuts_container", LONG_CLICK_EVENT));
         }
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
+    }
+
+    @Override
     protected String getLongPressIndicator() {
         return "deep_shortcuts_container";
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
index f8dd89c..37a7b91 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -41,6 +41,10 @@
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+    }
+
+    @Override
     protected String getLongPressIndicator() {
         return "drop_target_bar";
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index a769acf..69afcc4 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -117,8 +117,8 @@
             // part) one in the center, and parts of its right and left siblings. Find the
             // main task view by its width.
             final UiObject2 widestTask = Collections.max(taskViews,
-                    (t1, t2) -> Integer.compare(t1.getVisibleBounds().width(),
-                            t2.getVisibleBounds().width()));
+                    (t1, t2) -> Integer.compare(mLauncher.getVisibleBounds(t1).width(),
+                            mLauncher.getVisibleBounds(t2).width()));
 
             return new OverviewTask(mLauncher, widestTask, this);
         }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index d1a1254..2922acf 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -57,7 +57,7 @@
 
     private Background launch(BySelector selector) {
         LauncherInstrumentation.log("Launchable.launch before click " +
-                mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
+                mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
 
         mLauncher.executeAndWaitForEvent(
                 () -> mLauncher.clickLauncherObject(mObject),
@@ -92,9 +92,12 @@
                                     : launchableCenter.x + width / 2,
                             displaySize.y / 2),
                     getLongPressIndicator(),
-                    startsActivity);
+                    startsActivity,
+                    () -> addExpectedEventsForLongClick());
         }
     }
 
+    protected abstract void addExpectedEventsForLongClick();
+
     protected abstract String getLongPressIndicator();
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index d7374a4..710ce9e 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";
@@ -971,7 +977,7 @@
 
     int getBottomGestureMarginInContainer(UiObject2 container) {
         final int bottomGestureStartOnScreen = getRealDisplaySize().y - getBottomGestureSize();
-        return container.getVisibleBounds().bottom - bottomGestureStartOnScreen;
+        return getVisibleBounds(container).bottom - bottomGestureStartOnScreen;
     }
 
     void clickLauncherObject(UiObject2 object) {
@@ -989,10 +995,10 @@
             Collection<UiObject2> items,
             int topPaddingInContainer) {
         final UiObject2 lowestItem = Collections.max(items, (i1, i2) ->
-                Integer.compare(i1.getVisibleBounds().top, i2.getVisibleBounds().top));
+                Integer.compare(getVisibleBounds(i1).top, getVisibleBounds(i2).top));
 
-        final int itemRowCurrentTopOnScreen = lowestItem.getVisibleBounds().top;
-        final Rect containerRect = container.getVisibleBounds();
+        final int itemRowCurrentTopOnScreen = getVisibleBounds(lowestItem).top;
+        final Rect containerRect = getVisibleBounds(container);
         final int itemRowNewTopOnScreen = containerRect.top + topPaddingInContainer;
         final int distance = itemRowCurrentTopOnScreen - itemRowNewTopOnScreen + getTouchSlop();
 
@@ -1011,7 +1017,7 @@
 
     void scroll(
             UiObject2 container, Direction direction, Rect margins, int steps, boolean slowDown) {
-        final Rect rect = container.getVisibleBounds();
+        final Rect rect = getVisibleBounds(container);
         if (margins != null) {
             rect.left += margins.left;
             rect.top += margins.top;
@@ -1173,10 +1179,12 @@
     }
 
     @NonNull
-    UiObject2 clickAndGet(@NonNull final UiObject2 target, @NonNull String resName) {
+    UiObject2 clickAndGet(
+            @NonNull final UiObject2 target, @NonNull String resName, Pattern longClickEvent) {
         final Point targetCenter = target.getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter, GestureScope.INSIDE);
+        expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
         final UiObject2 result = waitForLauncherObject(resName);
         sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
                 GestureScope.INSIDE);
@@ -1326,4 +1334,13 @@
     void expectEvent(String sequence, Pattern expected) {
         if (sCheckingEvents) sEventChecker.expectPattern(sequence, expected);
     }
+
+    Rect getVisibleBounds(UiObject2 object) {
+        try {
+            return object.getVisibleBounds();
+        } catch (Throwable t) {
+            fail(t.toString());
+            return null;
+        }
+    }
 }
\ No newline at end of file
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);
     }
 
diff --git a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
index b8e6c0e..63a97f4 100644
--- a/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/OptionsPopupMenuItem.java
@@ -41,7 +41,7 @@
     public void launch(@NonNull String expectedPackageName) {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
             LauncherInstrumentation.log("OptionsPopupMenuItem before click "
-                    + mObject.getVisibleCenter() + " in " + mObject.getVisibleBounds());
+                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
             mLauncher.clickLauncherObject(mObject);
             if (!Build.MODEL.contains("Cuttlefish") ||
                     Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q &&
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 5c51782..fae5f19 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -56,7 +56,7 @@
                      "want to dismiss a task")) {
             verifyActiveContainer();
             // Dismiss the task via flinging it up.
-            final Rect taskBounds = mTask.getVisibleBounds();
+            final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
             final int centerX = taskBounds.centerX();
             final int centerY = taskBounds.centerY();
             mLauncher.linearGesture(centerX, centerY, centerX, 0, 10, false,
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index a658f16..53ef796 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -18,11 +18,17 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+import com.android.launcher3.testing.TestProtocol;
+
+import java.util.regex.Pattern;
+
 /**
  * Widget in workspace or a widget list.
  */
 public final class Widget extends Launchable {
 
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("Widgets.onLongClick");
+
     Widget(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
     }
@@ -35,4 +41,9 @@
     @Override
     protected void expectActivityStartEvents() {
     }
+
+    @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index a14d2f0..5be57c6 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -73,7 +73,7 @@
             mLauncher.scroll(
                     widgetsContainer,
                     Direction.UP,
-                    new Rect(0, 0, widgetsContainer.getVisibleBounds().width(), 0),
+                    new Rect(0, 0, mLauncher.getVisibleBounds(widgetsContainer).width(), 0),
                     FLING_STEPS, false);
             try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("flung back")) {
                 verifyActiveContainer();
@@ -111,19 +111,19 @@
 
                     int maxWidth = 0;
                     for (UiObject2 sibling : widget.getParent().getChildren()) {
-                        maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+                        maxWidth = Math.max(mLauncher.getVisibleBounds(sibling).width(), maxWidth);
                     }
 
-                    int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+                    int visibleDelta = maxWidth - mLauncher.getVisibleBounds(widget).width();
                     if (visibleDelta > 0) {
-                        Rect parentBounds = cell.getVisibleBounds();
+                        Rect parentBounds = mLauncher.getVisibleBounds(cell);
                         mLauncher.linearGesture(parentBounds.centerX() + visibleDelta
                                         + mLauncher.getTouchSlop(),
                                 parentBounds.centerY(), parentBounds.centerX(),
                                 parentBounds.centerY(), 10, true, GestureScope.INSIDE);
                     }
 
-                    if (widget.getVisibleBounds().bottom
+                    if (mLauncher.getVisibleBounds(widget).bottom
                             <= displaySize.y - mLauncher.getBottomGestureSize()) {
                         return new Widget(mLauncher, widget);
                     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 9ef6476..b3b5e32 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -52,6 +52,7 @@
     static final Pattern EVENT_CTRL_W_UP = Pattern.compile(
             "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_W"
                     + ".*?metaState=META_CTRL_ON");
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onWorkspaceItemLongClick");
 
     private final UiObject2 mHotseat;
 
@@ -176,9 +177,11 @@
                             mLauncher,
                             getHotseatAppIcon("Chrome"),
                             new Point(mLauncher.getDevice().getDisplayWidth(),
-                                    workspace.getVisibleBounds().centerY()),
+                                    mLauncher.getVisibleBounds(workspace).centerY()),
                             "deep_shortcuts_container",
-                            false);
+                            false,
+                            () -> mLauncher.expectEvent(
+                                    TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
                     verifyActiveContainer();
                 }
             }
@@ -199,7 +202,7 @@
 
     static void dragIconToWorkspace(
             LauncherInstrumentation launcher, Launchable launchable, Point dest,
-            String longPressIndicator, boolean startsActivity) {
+            String longPressIndicator, boolean startsActivity, Runnable expectLongClickEvents) {
         LauncherInstrumentation.log("dragIconToWorkspace: begin");
         final Point launchableCenter = launchable.getObject().getVisibleCenter();
         final long downTime = SystemClock.uptimeMillis();
@@ -208,6 +211,7 @@
                     launcher.sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN,
                             launchableCenter, LauncherInstrumentation.GestureScope.INSIDE);
                     LauncherInstrumentation.log("dragIconToWorkspace: sent down");
+                    expectLongClickEvents.run();
                     launcher.waitForLauncherObject(longPressIndicator);
                     LauncherInstrumentation.log("dragIconToWorkspace: indicator");
                     launcher.movePointer(launchableCenter, dest, 10, downTime, true,