Merge "If RecentsView is empty when starting quickswitch, listen for tasks to load" into ub-launcher3-master
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 630dd70..7ff8969 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+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.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
@@ -75,9 +76,11 @@
     protected static final Rect TEMP_RECT = new Rect();
 
     // Start resisting when swiping past this factor of mTransitionDragLength.
-    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = 1.4f;
+    private static final float DRAG_LENGTH_FACTOR_START_PULLBACK = ENABLE_OVERVIEW_ACTIONS.get()
+            ? 2.8f : 1.4f;
     // This is how far down we can scale down, where 0f is full screen and 1f is recents.
-    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = 1.8f;
+    private static final float DRAG_LENGTH_FACTOR_MAX_PULLBACK = ENABLE_OVERVIEW_ACTIONS.get()
+            ? 3.6f : 1.8f;
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
 
     // The distance needed to drag to reach the task size in recents.
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
new file mode 100644
index 0000000..6f919c1
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/FallbackNavBarTouchController.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.fallback;
+
+import android.view.MotionEvent;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.SysUINavigationMode;
+import com.android.quickstep.util.NavBarPosition;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
+
+/**
+ * In 0-button mode, intercepts swipe up from the nav bar on FallbackRecentsView to go home.
+ */
+public class FallbackNavBarTouchController implements TouchController {
+
+    private final RecentsActivity mActivity;
+    @Nullable
+    private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
+
+    public FallbackNavBarTouchController(RecentsActivity activity) {
+        mActivity = activity;
+        SysUINavigationMode.Mode sysUINavigationMode = SysUINavigationMode.getMode(mActivity);
+        if (sysUINavigationMode == SysUINavigationMode.Mode.NO_BUTTON) {
+            NavBarPosition navBarPosition = new NavBarPosition(sysUINavigationMode,
+                    DefaultDisplay.INSTANCE.get(mActivity).getInfo());
+            mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(mActivity,
+                    true /* disableHorizontalSwipe */, navBarPosition,
+                    null /* onInterceptTouch */, this::onSwipeUp);
+        } else {
+            mTriggerSwipeUpTracker = null;
+        }
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        boolean cameFromNavBar = (ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) != 0;
+        if (cameFromNavBar && mTriggerSwipeUpTracker != null) {
+            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                mTriggerSwipeUpTracker.init();
+            }
+            onControllerTouchEvent(ev);
+            return mTriggerSwipeUpTracker.interceptedTouch();
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        if (mTriggerSwipeUpTracker != null) {
+            mTriggerSwipeUpTracker.onMotionEvent(ev);
+            return true;
+        }
+        return false;
+    }
+
+    private void onSwipeUp(boolean wasFling) {
+        mActivity.<FallbackRecentsView>getOverviewPanel().startHome();
+    }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index 1820729..de5fd7c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -48,7 +48,10 @@
     }
 
     public void setup() {
-        mControllers = new TouchController[] { new RecentsTaskController(mActivity) };
+        mControllers = new TouchController[] {
+                new RecentsTaskController(mActivity),
+                new FallbackNavBarTouchController(mActivity),
+        };
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 875ec29..ca15ca1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -15,23 +15,12 @@
  */
 package com.android.quickstep.inputconsumers;
 
-import static android.view.MotionEvent.ACTION_CANCEL;
-import static android.view.MotionEvent.ACTION_DOWN;
-import static android.view.MotionEvent.ACTION_MOVE;
-import static android.view.MotionEvent.ACTION_UP;
-
-import static com.android.launcher3.Utilities.squaredHypot;
-
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.PointF;
 import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.ViewConfiguration;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.logging.StatsLogUtils;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -39,31 +28,22 @@
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.TriggerSwipeUpTouchTracker;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
 public class OverviewWithoutFocusInputConsumer implements InputConsumer {
 
     private final Context mContext;
-    private final RecentsAnimationDeviceState mDeviceState;
-    private final GestureState mGestureState;
     private final InputMonitorCompat mInputMonitor;
-    private final boolean mDisableHorizontalSwipe;
-    private final PointF mDownPos = new PointF();
-    private final float mSquaredTouchSlop;
-
-    private boolean mInterceptedTouch;
-    private VelocityTracker mVelocityTracker;
+    private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
 
     public OverviewWithoutFocusInputConsumer(Context context,
             RecentsAnimationDeviceState deviceState, GestureState gestureState,
             InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
         mContext = context;
-        mDeviceState = deviceState;
-        mGestureState = gestureState;
         mInputMonitor = inputMonitor;
-        mDisableHorizontalSwipe = disableHorizontalSwipe;
-        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
-        mVelocityTracker = VelocityTracker.obtain();
+        mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, disableHorizontalSwipe,
+                deviceState.getNavBarPosition(), this::onInterceptTouch, this::onSwipeUp);
     }
 
     @Override
@@ -73,97 +53,31 @@
 
     @Override
     public boolean allowInterceptByParent() {
-        return !mInterceptedTouch;
-    }
-
-    private void endTouchTracking() {
-        if (mVelocityTracker != null) {
-            mVelocityTracker.recycle();
-            mVelocityTracker = null;
-        }
+        return !mTriggerSwipeUpTracker.interceptedTouch();
     }
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        if (mVelocityTracker == null) {
-            return;
-        }
+        mTriggerSwipeUpTracker.onMotionEvent(ev);
+    }
 
-        mVelocityTracker.addMovement(ev);
-        switch (ev.getActionMasked()) {
-            case ACTION_DOWN: {
-                mDownPos.set(ev.getX(), ev.getY());
-                break;
-            }
-            case ACTION_MOVE: {
-                if (!mInterceptedTouch) {
-                    float displacementX = ev.getX() - mDownPos.x;
-                    float displacementY = ev.getY() - mDownPos.y;
-                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
-                        if (mDisableHorizontalSwipe
-                                && Math.abs(displacementX) > Math.abs(displacementY)) {
-                            // Horizontal gesture is not allowed in this region
-                            endTouchTracking();
-                            break;
-                        }
-
-                        mInterceptedTouch = true;
-
-                        if (mInputMonitor != null) {
-                            mInputMonitor.pilferPointers();
-                        }
-                    }
-                }
-                break;
-            }
-
-            case ACTION_CANCEL:
-                endTouchTracking();
-                break;
-
-            case ACTION_UP: {
-                finishTouchTracking(ev);
-                endTouchTracking();
-                break;
-            }
+    private void onInterceptTouch() {
+        if (mInputMonitor != null) {
+            mInputMonitor.pilferPointers();
         }
     }
 
-    private void finishTouchTracking(MotionEvent ev) {
-        mVelocityTracker.computeCurrentVelocity(100);
-        float velocityX = mVelocityTracker.getXVelocity();
-        float velocityY = mVelocityTracker.getYVelocity();
-        float velocity = mDeviceState.getNavBarPosition().isRightEdge()
-                ? -velocityX
-                : mDeviceState.getNavBarPosition().isLeftEdge()
-                        ? velocityX
-                        : -velocityY;
-
-        final boolean triggerQuickstep;
-        int touch = Touch.FLING;
-        if (Math.abs(velocity) >= ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity()) {
-            triggerQuickstep = velocity > 0;
-        } else {
-            float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
-            float displacementY = ev.getY() - mDownPos.y;
-            triggerQuickstep = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
-            touch = Touch.SWIPE;
-        }
-
-        if (triggerQuickstep) {
-            mContext.startActivity(new Intent(Intent.ACTION_MAIN)
-                    .addCategory(Intent.CATEGORY_HOME)
-                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-            ActiveGestureLog.INSTANCE.addLog("startQuickstep");
-            BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
-            int pageIndex = -1; // This number doesn't reflect workspace page index.
-                                // It only indicates that launcher client screen was shown.
-            int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
-            activity.getUserEventDispatcher().logActionOnContainer(
-                    touch, Direction.UP, containerType, pageIndex);
-            activity.getUserEventDispatcher().setPreviousHomeGesture(true);
-        } else {
-            // ignore
-        }
+    private void onSwipeUp(boolean wasFling) {
+        mContext.startActivity(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        ActiveGestureLog.INSTANCE.addLog("startQuickstep");
+        BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+        int pageIndex = -1; // This number doesn't reflect workspace page index.
+                            // It only indicates that launcher client screen was shown.
+        int containerType = StatsLogUtils.getContainerTypeFromState(activity.getCurrentState());
+        activity.getUserEventDispatcher().logActionOnContainer(
+                wasFling ? Touch.FLING : Touch.SWIPE, Direction.UP, containerType, pageIndex);
+        activity.getUserEventDispatcher().setPreviousHomeGesture(true);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
new file mode 100644
index 0000000..c71258b
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/TriggerSwipeUpTouchTracker.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import com.android.launcher3.Utilities;
+
+/**
+ * Tracks motion events to determine whether a gesture on the nav bar is a swipe up.
+ */
+public class TriggerSwipeUpTouchTracker {
+
+    private final PointF mDownPos = new PointF();
+    private final float mSquaredTouchSlop;
+    private final float mMinFlingVelocity;
+    private final boolean mDisableHorizontalSwipe;
+    private final NavBarPosition mNavBarPosition;
+    private final Runnable mOnInterceptTouch;
+    private final OnSwipeUpListener mOnSwipeUp;
+
+    private boolean mInterceptedTouch;
+    private VelocityTracker mVelocityTracker;
+
+    public TriggerSwipeUpTouchTracker(Context context, boolean disableHorizontalSwipe,
+            NavBarPosition navBarPosition, Runnable onInterceptTouch,
+            OnSwipeUpListener onSwipeUp) {
+        mSquaredTouchSlop = Utilities.squaredTouchSlop(context);
+        mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+        mNavBarPosition = navBarPosition;
+        mDisableHorizontalSwipe = disableHorizontalSwipe;
+        mOnInterceptTouch = onInterceptTouch;
+        mOnSwipeUp = onSwipeUp;
+
+        init();
+    }
+
+    /**
+     * Reset some initial values to prepare for the next gesture.
+     */
+    public void init() {
+        mInterceptedTouch = false;
+        mVelocityTracker = VelocityTracker.obtain();
+    }
+
+    /**
+     * @return Whether we have passed the touch slop and are still tracking the gesture.
+     */
+    public boolean interceptedTouch() {
+        return mInterceptedTouch;
+    }
+
+    /**
+     * Track motion events to determine whether an atomic swipe up has occurred.
+     */
+    public void onMotionEvent(MotionEvent ev) {
+        if (mVelocityTracker == null) {
+            return;
+        }
+
+        mVelocityTracker.addMovement(ev);
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                break;
+            }
+            case ACTION_MOVE: {
+                if (!mInterceptedTouch) {
+                    float displacementX = ev.getX() - mDownPos.x;
+                    float displacementY = ev.getY() - mDownPos.y;
+                    if (squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop) {
+                        if (mDisableHorizontalSwipe
+                                && Math.abs(displacementX) > Math.abs(displacementY)) {
+                            // Horizontal gesture is not allowed in this region
+                            endTouchTracking();
+                            break;
+                        }
+
+                        mInterceptedTouch = true;
+
+                        if (mOnInterceptTouch != null) {
+                            mOnInterceptTouch.run();
+                        }
+                    }
+                }
+                break;
+            }
+
+            case ACTION_CANCEL:
+                endTouchTracking();
+                break;
+
+            case ACTION_UP: {
+                onGestureEnd(ev);
+                endTouchTracking();
+                break;
+            }
+        }
+    }
+
+    private void endTouchTracking() {
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+    }
+
+    private void onGestureEnd(MotionEvent ev) {
+        mVelocityTracker.computeCurrentVelocity(1000);
+        float velocityX = mVelocityTracker.getXVelocity();
+        float velocityY = mVelocityTracker.getYVelocity();
+        float velocity = mNavBarPosition.isRightEdge()
+                ? -velocityX
+                : mNavBarPosition.isLeftEdge()
+                        ? velocityX
+                        : -velocityY;
+
+        final boolean wasFling = Math.abs(velocity) >= mMinFlingVelocity;
+        final boolean isSwipeUp;
+        if (wasFling) {
+            isSwipeUp = velocity > 0;
+        } else {
+            float displacementX = mDisableHorizontalSwipe ? 0 : (ev.getX() - mDownPos.x);
+            float displacementY = ev.getY() - mDownPos.y;
+            isSwipeUp = squaredHypot(displacementX, displacementY) >= mSquaredTouchSlop;
+        }
+
+        if (isSwipeUp && mOnSwipeUp != null) {
+            mOnSwipeUp.onSwipeUp(wasFling);
+        }
+    }
+
+    /**
+     * Callback when the gesture ends and was determined to be a swipe from the nav bar.
+     */
+    public interface OnSwipeUpListener {
+        /**
+         * Called on touch up if a swipe up was detected.
+         * @param wasFling Whether the swipe was a fling, or just passed touch slop at low velocity.
+         */
+        void onSwipeUp(boolean wasFling);
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LayoutUtils.java b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
index d49ff89..b249f48 100644
--- a/quickstep/src/com/android/quickstep/util/LayoutUtils.java
+++ b/quickstep/src/com/android/quickstep/util/LayoutUtils.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.content.Context;
@@ -26,7 +28,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.quickstep.SysUINavigationMode;
 
 import java.lang.annotation.Retention;
@@ -58,7 +59,7 @@
         } else {
             Resources res = context.getResources();
 
-            if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+            if (ENABLE_OVERVIEW_ACTIONS.get()) {
                 //TODO: this needs to account for the swipe gesture height and accessibility
                 // UI when shown.
                 extraSpace = 0;
@@ -111,7 +112,7 @@
             final int paddingResId;
             if (dp.isVerticalBarLayout()) {
                 paddingResId = R.dimen.landscape_task_card_horz_space;
-            } else if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+            } else if (ENABLE_OVERVIEW_ACTIONS.get()) {
                 paddingResId = R.dimen.portrait_task_card_horz_space_big_overview;
             } else {
                 paddingResId = R.dimen.portrait_task_card_horz_space;
@@ -146,6 +147,11 @@
 
     public static int getShelfTrackingDistance(Context context, DeviceProfile dp) {
         // Track the bottom of the window.
+        if (ENABLE_OVERVIEW_ACTIONS.get()) {
+            Rect taskSize = new Rect();
+            calculateLauncherTaskSize(context, dp, taskSize);
+            return (dp.heightPx - taskSize.height()) / 2;
+        }
         int shelfHeight = dp.hotseatBarSizePx + dp.getInsets().bottom;
         int spaceBetweenShelfAndRecents = (int) context.getResources().getDimension(
                 R.dimen.task_card_vert_space);
@@ -157,7 +163,7 @@
      * @return the margin in pixels.
      */
     public static int thumbnailBottomMargin(Resources resources) {
-        if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+        if (ENABLE_OVERVIEW_ACTIONS.get()) {
             return resources.getDimensionPixelSize(R.dimen.overview_actions_height);
         } else {
             return 0;
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 428e647..ab6393a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -100,6 +100,7 @@
     @PortraitLandscape
     public void testOverview() throws Exception {
         startTestApps();
+        // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.pressHome().switchToOverview();
         assertTrue("Launcher internal state didn't switch to Overview",
                 isInState(LauncherState.OVERVIEW));
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 72c95c4..b764a07 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -351,7 +351,9 @@
     }
 
     public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
-        mNotificationItemView.applyNotificationInfos(notificationInfos);
+        if (mNotificationItemView != null) {
+            mNotificationItemView.applyNotificationInfos(notificationInfos);
+        }
     }
 
     private void updateHiddenShortcuts() {
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
new file mode 100644
index 0000000..024110d
--- /dev/null
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -0,0 +1,29 @@
+/*
+ * 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.testing;
+
+import android.util.Log;
+
+import com.android.launcher3.Utilities;
+
+public final class TestLogging {
+    public static synchronized void recordEvent(String event) {
+        if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
+            Log.d(TestProtocol.TAPL_EVENTS_TAG, event);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 929315a..01c207f 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -31,6 +31,7 @@
     public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
     public static final int ALL_APPS_STATE_ORDINAL = 5;
     public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+    public static final String TAPL_EVENTS_TAG = "TaplEvents";
 
     public static String stateOrdinalToString(int ordinal) {
         switch (ordinal) {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index f470edb..d193bef 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -508,7 +508,7 @@
             mAtomicComponentsController.getAnimationPlayer().end();
             mAtomicComponentsController = null;
         }
-        cancelAnimationControllers();
+        clearState();
         boolean shouldGoToTargetState = true;
         if (mPendingAnimation != null) {
             boolean reachedTarget = mToState == targetState;
@@ -546,13 +546,13 @@
             mAtomicAnim = null;
         }
         mScheduleResumeAtomicComponent = false;
+        mDetector.finishedScrolling();
+        mDetector.setDetectableScrollConditions(0, false);
     }
 
     private void cancelAnimationControllers() {
         mCurrentAnimation = null;
         cancelAtomicComponentsController();
-        mDetector.finishedScrolling();
-        mDetector.setDetectableScrollConditions(0, false);
     }
 
     private void cancelAtomicComponentsController() {
diff --git a/src/com/android/launcher3/touch/BaseSwipeDetector.java b/src/com/android/launcher3/touch/BaseSwipeDetector.java
index 12ca5ee..30283da 100644
--- a/src/com/android/launcher3/touch/BaseSwipeDetector.java
+++ b/src/com/android/launcher3/touch/BaseSwipeDetector.java
@@ -24,6 +24,10 @@
 import android.view.ViewConfiguration;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.LinkedList;
+import java.util.Queue;
 
 /**
  * Scroll/drag/swipe gesture detector.
@@ -49,13 +53,15 @@
     protected final boolean mIsRtl;
     protected final float mTouchSlop;
     protected final float mMaxVelocity;
+    private final Queue<Runnable> mSetStateQueue = new LinkedList<>();
 
     private int mActivePointerId = INVALID_POINTER_ID;
     private VelocityTracker mVelocityTracker;
     private PointF mLastDisplacement = new PointF();
     private PointF mDisplacement = new PointF();
     protected PointF mSubtractDisplacement = new PointF();
-    private ScrollState mState = ScrollState.IDLE;
+    @VisibleForTesting ScrollState mState = ScrollState.IDLE;
+    private boolean mIsSettingState;
 
     protected boolean mIgnoreSlopWhenSettling;
 
@@ -195,6 +201,12 @@
     // SETTLING -> (View settled) -> IDLE
 
     private void setState(ScrollState newState) {
+        if (mIsSettingState) {
+            mSetStateQueue.add(() -> setState(newState));
+            return;
+        }
+        mIsSettingState = true;
+
         if (DBG) {
             Log.d(TAG, "setState:" + mState + "->" + newState);
         }
@@ -212,6 +224,10 @@
         }
 
         mState = newState;
+        mIsSettingState = false;
+        if (!mSetStateQueue.isEmpty()) {
+            mSetStateQueue.remove().run();
+        }
     }
 
     private void initializeDragging() {
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index ba1bfa5..bee7853 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.folder.Folder;
+import com.android.launcher3.testing.TestLogging;
 
 /**
  * Class to handle long-clicks on workspace items and start drag as a result.
@@ -46,6 +47,7 @@
             ItemLongClickListener::onAllAppsItemLongClick;
 
     private static boolean onWorkspaceItemLongClick(View v) {
+        TestLogging.recordEvent("onWorkspaceItemLongClick");
         Launcher launcher = Launcher.getLauncher(v.getContext());
         if (!canStartDrag(launcher)) return false;
         if (!launcher.isInState(NORMAL) && !launcher.isInState(OVERVIEW)) return false;
@@ -75,6 +77,7 @@
     }
 
     private static boolean onAllAppsItemLongClick(View v) {
+        TestLogging.recordEvent("onAllAppsItemLongClick");
         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 66fdc94..61ce046 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -25,7 +25,6 @@
 
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.util.Log;
 import android.view.GestureDetector;
 import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
@@ -37,10 +36,9 @@
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.testing.TestProtocol;
+import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.views.OptionsPopupView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -168,6 +166,7 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
+        TestLogging.recordEvent("Workspace.longPress");
         if (mLongPressState == STATE_REQUESTED) {
             if (canHandleLongPress()) {
                 mLongPressState = STATE_PENDING_PARENT_INFORM;
@@ -178,9 +177,6 @@
                 mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS,
                         Action.Direction.NONE, ContainerType.WORKSPACE,
                         mWorkspace.getCurrentPage());
-                if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-                    Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on long press");
-                }
                 OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
             } else {
                 cancelLongPress();
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index 6cae43d..3758cb8 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -35,6 +35,7 @@
 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.touch.ItemLongClickListener;
 import com.android.launcher3.uioverrides.WallpaperColorInfo;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -90,6 +91,7 @@
 
     @Override
     public boolean onLongClick(View v) {
+        TestLogging.recordEvent("Widgets.onLongClick");
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
 
         if (v instanceof WidgetCell) {
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 5174e4d..6d463b5 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -21,9 +21,11 @@
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
 
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyObject;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -168,4 +170,21 @@
         // TODO: actually calculate the following parameters and do exact value checks.
         verify(mMockListener).onDragEnd(anyFloat());
     }
+
+    @Test
+    public void testInterleavedSetState() {
+        doAnswer(invocationOnMock -> {
+            // Sets state to IDLE. (Normally onDragEnd() will have state SETTLING.)
+            mDetector.finishedScrolling();
+            return null;
+        }).when(mMockListener).onDragEnd(anyFloat());
+
+        mGenerator.put(0, 100, 100);
+        mGenerator.move(0, 100, 100 + mTouchSlop);
+        mGenerator.move(0, 100, 100 + mTouchSlop * 2);
+        mGenerator.lift(0);
+        verify(mMockListener).onDragEnd(anyFloat());
+        assertTrue("SwipeDetector should be IDLE but was " + mDetector.mState,
+                mDetector.isIdleState());
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 2da6344..bf68f71 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -22,10 +22,15 @@
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
 
+import java.util.regex.Pattern;
+
 /**
  * App icon, whether in all apps or in workspace/
  */
 public final class AppIcon extends Launchable {
+
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
+
     AppIcon(LauncherInstrumentation launcher, UiObject2 icon) {
         super(launcher, icon);
     }
@@ -43,6 +48,11 @@
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(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 ba9c10e..597be90 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenuItem.java
@@ -34,6 +34,10 @@
     }
 
     @Override
+    protected void addExpectedEventsForLongClick() {
+    }
+
+    @Override
     protected String getLongPressIndicator() {
         return "drop_target_bar";
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index 6881197..9327cb3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -69,18 +69,24 @@
      * Drags an object to the center of homescreen.
      */
     public void dragToWorkspace() {
-        final Point launchableCenter = getObject().getVisibleCenter();
-        final Point displaySize = mLauncher.getRealDisplaySize();
-        final int width = displaySize.x / 2;
-        Workspace.dragIconToWorkspace(
-                mLauncher,
-                this,
-                new Point(
-                        launchableCenter.x >= width ?
-                                launchableCenter.x - width / 2 : launchableCenter.x + width / 2,
-                        displaySize.y / 2),
-                getLongPressIndicator());
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            final Point launchableCenter = getObject().getVisibleCenter();
+            final Point displaySize = mLauncher.getRealDisplaySize();
+            final int width = displaySize.x / 2;
+            addExpectedEventsForLongClick();
+            Workspace.dragIconToWorkspace(
+                    mLauncher,
+                    this,
+                    new Point(
+                            launchableCenter.x >= width
+                                    ? launchableCenter.x - width / 2
+                                    : launchableCenter.x + width / 2,
+                            displaySize.y / 2),
+                    getLongPressIndicator());
+        }
     }
 
+    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 95c4997..196c6b7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -79,6 +79,8 @@
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
 /**
@@ -92,6 +94,13 @@
     private static final int GESTURE_STEP_MS = 16;
     private static long START_TIME = System.currentTimeMillis();
 
+    static final Pattern LOG_TIME = Pattern.compile(
+            "[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9]");
+
+    static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
+            "(?<time>[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
+                    + ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
+
     // Types for launcher containers that the user is interacting with. "Background" is a
     // pseudo-container corresponding to inactive launcher covered by another app.
     public enum ContainerType {
@@ -146,6 +155,11 @@
 
     private Consumer<ContainerType> mOnSettledStateAction;
 
+    // Not null when we are collecting expected events to compare with actual ones.
+    private List<Pattern> mExpectedEvents;
+
+    private String mTimeBeforeFirstLogEvent;
+
     /**
      * Constructs the root of TAPL hierarchy. You get all other objects from it.
      */
@@ -299,8 +313,11 @@
     public void checkForAnomaly() {
         final String anomalyMessage = getAnomalyMessage();
         if (anomalyMessage != null) {
-            failWithSystemHealth(
-                    "Tests are broken by a non-Launcher system error: " + anomalyMessage);
+            String message = "Tests are broken by a non-Launcher system error: " + anomalyMessage;
+            log("Hierarchy dump for: " + message);
+            dumpViewHierarchy();
+
+            Assert.fail(formatSystemHealthMessage(message));
         }
     }
 
@@ -339,7 +356,7 @@
         mOnSettledStateAction = onSettledStateAction;
     }
 
-    private String getSystemHealthMessage() {
+    private String formatSystemHealthMessage(String message) {
         final String testPackage = getContext().getPackageName();
 
         mInstrumentation.getUiAutomation().grantRuntimePermission(
@@ -347,30 +364,34 @@
         mInstrumentation.getUiAutomation().grantRuntimePermission(
                 testPackage, "android.permission.PACKAGE_USAGE_STATS");
 
-        return mSystemHealthSupplier != null
+        final String systemHealth = mSystemHealthSupplier != null
                 ? mSystemHealthSupplier.apply(START_TIME)
                 : TestHelpers.getSystemHealthMessage(getContext(), START_TIME);
+
+        if (systemHealth != null) {
+            return message
+                    + ",\nperhaps linked to system health problems:\n<<<<<<<<<<<<<<<<<<\n"
+                    + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
+        }
+
+        return message;
     }
 
     private void fail(String message) {
         checkForAnomaly();
 
-        failWithSystemHealth("http://go/tapl : " + getContextDescription() + message +
-                " (visible state: " + getVisibleStateMessage() + ")");
-    }
-
-    private void failWithSystemHealth(String message) {
-        final String systemHealth = getSystemHealthMessage();
-        if (systemHealth != null) {
-            message = message
-                    + ", perhaps because of system health problems:\n<<<<<<<<<<<<<<<<<<\n"
-                    + systemHealth + "\n>>>>>>>>>>>>>>>>>>";
-        }
-
+        message = "http://go/tapl : " + getContextDescription() + message
+                + " (visible state: " + getVisibleStateMessage() + ")";
         log("Hierarchy dump for: " + message);
         dumpViewHierarchy();
 
-        Assert.fail(message);
+        final String eventMismatch = getEventMismatchMessage();
+
+        if (eventMismatch != null) {
+            message = message + ",\nhaving produced wrong events:\n    " + eventMismatch;
+        }
+
+        Assert.fail(formatSystemHealthMessage(message));
     }
 
     private String getContextDescription() {
@@ -582,7 +603,7 @@
             try (LauncherInstrumentation.Closable c = addContextLayer(action)) {
                 mDevice.waitForIdle();
                 runToState(
-                        () -> waitForSystemUiObject("home").click(),
+                        waitForSystemUiObject("home")::click,
                         NORMAL_STATE_ORDINAL,
                         !hasLauncherObject(WORKSPACE_RES_ID)
                                 && (hasLauncherObject(APPS_RES_ID)
@@ -1099,4 +1120,104 @@
         }
         return tasks;
     }
+
+    private List<String> getEvents() {
+        final ArrayList<String> events = new ArrayList<>();
+        try {
+            final String logcatTimeParameter =
+                    mTimeBeforeFirstLogEvent != null ? " -t " + mTimeBeforeFirstLogEvent : "";
+            final String logcatEvents = mDevice.executeShellCommand(
+                    "logcat -d --pid=" + getPid() + logcatTimeParameter
+                            + " -s " + TestProtocol.TAPL_EVENTS_TAG);
+            final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
+            while (matcher.find()) {
+                final String eventTime = matcher.group("time");
+                if (eventTime.equals(mTimeBeforeFirstLogEvent)) continue;
+
+                events.add(matcher.group("event"));
+            }
+            return events;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void startRecordingEvents() {
+        Assert.assertTrue("Already recording events", mExpectedEvents == null);
+        mExpectedEvents = new ArrayList<>();
+
+        try {
+            final String lastLogLine =
+                    mDevice.executeShellCommand("logcat -d --pid=" + getPid() + " -t 1");
+            final Matcher matcher = LOG_TIME.matcher(lastLogLine);
+            mTimeBeforeFirstLogEvent = matcher.find() ? matcher.group().replaceAll(" ", "") : null;
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void stopRecordingEvents() {
+        mExpectedEvents = null;
+    }
+
+    Closable eventsCheck() {
+        // Entering events check block.
+        startRecordingEvents();
+
+        return () -> {
+            // Leaving events check block.
+            if (mExpectedEvents == null) {
+                return; // There was a failure. Noo need to report another one.
+            }
+
+            // Wait until Launcher generates expected number of events.
+            final long endTime = SystemClock.uptimeMillis() + WAIT_TIME_MS;
+            while (SystemClock.uptimeMillis() < endTime
+                    && getEvents().size() < mExpectedEvents.size()) {
+                SystemClock.sleep(100);
+            }
+
+            final String message = getEventMismatchMessage();
+            if (message != null) {
+                Assert.fail(formatSystemHealthMessage(
+                        "http://go/tapl : unexpected event sequence: " + message));
+            }
+        };
+    }
+
+    void expectEvent(Pattern expected) {
+        if (mExpectedEvents != null) mExpectedEvents.add(expected);
+    }
+
+    private String getEventMismatchMessage() {
+        if (mExpectedEvents == null) return null;
+
+        try {
+            final List<String> actual = getEvents();
+
+            for (int i = 0; i < mExpectedEvents.size(); ++i) {
+                if (i >= actual.size()) {
+                    return formatEventMismatchMessage("too few actual events", actual, i);
+                }
+                if (!mExpectedEvents.get(i).matcher(actual.get(i)).find()) {
+                    return formatEventMismatchMessage("mismatched event", actual, i);
+                }
+            }
+
+            if (actual.size() > mExpectedEvents.size()) {
+                return formatEventMismatchMessage(
+                        "too many actual events", actual, mExpectedEvents.size());
+            }
+        } finally {
+            stopRecordingEvents();
+        }
+
+        return null;
+    }
+
+    private String formatEventMismatchMessage(String message, List<String> actual, int position) {
+        return message + ", pos=" + position
+                + ", expected=" + mExpectedEvents
+                + ", actual" + actual;
+    }
 }
\ No newline at end of file
diff --git a/tests/tapl/com/android/launcher3/tapl/Widget.java b/tests/tapl/com/android/launcher3/tapl/Widget.java
index 1b6d8c4..e0db16d 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widget.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widget.java
@@ -18,10 +18,15 @@
 
 import androidx.test.uiautomator.UiObject2;
 
+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);
     }
@@ -30,4 +35,9 @@
     protected String getLongPressIndicator() {
         return "drop_target_bar";
     }
+
+    @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(LONG_CLICK_EVENT);
+    }
 }