Merge "Ensure Gesture Nav Edu animation scales to fit different screen sizes" into udc-qpr-dev
diff --git a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
index f99155f..29b24b7 100644
--- a/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
+++ b/ext_tests/src/com/android/launcher3/testing/DebugTestInformationHandler.java
@@ -25,7 +25,6 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.system.Os;
-import android.util.Log;
 
 import androidx.annotation.Keep;
 import androidx.annotation.Nullable;
@@ -62,7 +61,6 @@
                 public void onActivityCreated(Activity activity, Bundle bundle) {
                     sActivities.put(activity, true);
                     ++sActivitiesCreatedCount;
-                    Log.d(TestProtocol.FLAKY_ACTIVITY_COUNT, "onActivityCreated", new Exception());
                 }
 
                 @Override
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 619bef2..87a9ecb 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -27,6 +27,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.ComponentName;
+import android.util.Log;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
 import android.view.ViewGroup;
@@ -74,6 +75,7 @@
         SystemShortcut.Factory<QuickstepLauncher>, DeviceProfile.OnDeviceProfileChangeListener,
         DragSource, ViewGroup.OnHierarchyChangeListener {
 
+    private static final String TAG = "HotseatPredictionController";
     private static final int FLAG_UPDATE_PAUSED = 1 << 0;
     private static final int FLAG_DRAG_IN_PROGRESS = 1 << 1;
     private static final int FLAG_FILL_IN_PROGRESS = 1 << 2;
@@ -183,6 +185,7 @@
     }
 
     private void fillGapsWithPrediction(boolean animate) {
+        Log.d(TAG, "fillGapsWithPrediction");
         if (mPauseFlags != 0) {
             return;
         }
@@ -207,12 +210,16 @@
             View child = mHotseat.getChildAt(
                     mHotseat.getCellXFromOrder(rank),
                     mHotseat.getCellYFromOrder(rank));
+            Log.d(TAG, "Hotseat app child is: " + child + " and isPredictedIcon() evaluates to"
+                    + ": " + isPredictedIcon(child));
 
             if (child != null && !isPredictedIcon(child)) {
                 continue;
             }
             if (mPredictedItems.size() <= predictionIndex) {
                 // Remove predicted apps from the past
+                Log.d(TAG, "Remove predicted apps from the past\nPrediction Index: "
+                        + predictionIndex);
                 if (isPredictedIcon(child)) {
                     mHotseat.removeView(child);
                 }
@@ -220,6 +227,11 @@
             }
             WorkspaceItemInfo predictedItem =
                     (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
+            Log.d(TAG, "Predicted item is: " + predictedItem);
+            if (child != null) {
+                Log.d(TAG, "Predicted item is enabled: " + child.isEnabled());
+            }
+
             if (isPredictedIcon(child) && child.isEnabled()) {
                 PredictedAppIcon icon = (PredictedAppIcon) child;
                 boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
@@ -239,6 +251,7 @@
     }
 
     private void bindItems(List<WorkspaceItemInfo> itemsToAdd, boolean animate) {
+        Log.d(TAG, "bindItems to hotseat: " + itemsToAdd);
         AnimatorSet animationSet = new AnimatorSet();
         for (WorkspaceItemInfo item : itemsToAdd) {
             PredictedAppIcon icon = PredictedAppIcon.createIcon(mHotseat, item);
@@ -292,8 +305,10 @@
     public void setPredictedItems(FixedContainerItems items) {
         mPredictedItems = new ArrayList(items.items);
         if (mPredictedItems.isEmpty()) {
+            Log.d(TAG, "Predicted items is initially empty");
             HotseatRestoreHelper.restoreBackup(mLauncher);
         }
+        Log.d(TAG, "Predicted items: " + mPredictedItems);
         fillGapsWithPrediction();
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index b444b49..24bc58f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -73,6 +73,7 @@
 import android.os.IBinder;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.util.Log;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
 import android.view.View;
@@ -190,6 +191,8 @@
     private static final String TRACE_RELAYOUT_CLASS =
             SystemProperties.get("persist.debug.trace_request_layout_class", null);
 
+    private static final String TAG = "QuickstepLauncher";
+
     public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
 
     protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
@@ -434,6 +437,7 @@
 
     @Override
     public void bindExtraContainerItems(FixedContainerItems item) {
+        Log.d(TAG, "Bind extra container items");
         if (item.containerId == Favorites.CONTAINER_PREDICTION) {
             mAllAppsPredictions = item;
             PredictionRowView<?> predictionRowView =
@@ -441,6 +445,7 @@
                             PredictionRowView.class);
             predictionRowView.setPredictedApps(item.items);
         } else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            Log.d(TAG, "Bind extra container item is hotseat prediction");
             mHotseatPredictionController.setPredictedItems(item);
         } else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
             getPopupDataProvider().setRecommendedWidgets(item.items);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 4075388..ca598c8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -119,9 +119,6 @@
     protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
         if (fromState == NORMAL && mDidTouchStartInNavBar) {
             return HINT_STATE;
-        } else if (fromState == OVERVIEW && isDragTowardPositive) {
-            // Don't allow swiping up to all apps.
-            return OVERVIEW;
         }
         return super.getTargetState(fromState, isDragTowardPositive);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 454a1f5..e30fe66 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -96,8 +96,6 @@
             return FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()
                     ? mLauncher.getStateManager().getLastState()
                     : NORMAL;
-        } else if (fromState == OVERVIEW) {
-            return isDragTowardPositive ? OVERVIEW : NORMAL;
         } else if (fromState == NORMAL && isDragTowardPositive) {
             return ALL_APPS;
         }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0b5a070..0de4ffc 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -125,13 +125,14 @@
             // If mCallbacks still != null, that means we are getting this startRecentsAnimation()
             // before the previous one got onRecentsAnimationStart(). In that case, cleanup the
             // previous animation so it doesn't mess up/listen to state changes in this animation.
-            cleanUpRecentsAnimation();
+            cleanUpRecentsAnimation(mCallbacks);
         }
 
         final BaseActivityInterface activityInterface = gestureState.getActivityInterface();
         mLastGestureState = gestureState;
-        mCallbacks = new RecentsAnimationCallbacks(SystemUiProxy.INSTANCE.get(mCtx),
-                activityInterface.allowMinimizeSplitScreen());
+        RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
+                SystemUiProxy.INSTANCE.get(mCtx), activityInterface.allowMinimizeSplitScreen());
+        mCallbacks = newCallbacks;
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
             public void onRecentsAnimationStart(RecentsAnimationController controller,
@@ -157,12 +158,12 @@
 
             @Override
             public void onRecentsAnimationCanceled(HashMap<Integer, ThumbnailData> thumbnailDatas) {
-                cleanUpRecentsAnimation();
+                cleanUpRecentsAnimation(newCallbacks);
             }
 
             @Override
             public void onRecentsAnimationFinished(RecentsAnimationController controller) {
-                cleanUpRecentsAnimation();
+                cleanUpRecentsAnimation(newCallbacks);
             }
 
             @Override
@@ -334,7 +335,6 @@
         if (mController != null) {
             ActiveGestureLog.INSTANCE.addLog(
                     /* event= */ "finishRunningRecentsAnimation", toHome);
-            mCallbacks.notifyAnimationCanceled();
             if (forceFinish) {
                 mController.finishController(toHome, null, false /* sendUserLeaveHint */,
                         true /* forceFinish */);
@@ -343,7 +343,6 @@
                         ? mController::finishAnimationToHome
                         : mController::finishAnimationToApp);
             }
-            cleanUpRecentsAnimation();
         }
     }
 
@@ -370,7 +369,12 @@
     /**
      * Cleans up the recents animation entirely.
      */
-    private void cleanUpRecentsAnimation() {
+    private void cleanUpRecentsAnimation(RecentsAnimationCallbacks targetCallbacks) {
+        if (mCallbacks != targetCallbacks) {
+            ActiveGestureLog.INSTANCE.addLog(
+                    /* event= */ "cleanUpRecentsAnimation skipped due to wrong callbacks");
+            return;
+        }
         ActiveGestureLog.INSTANCE.addLog(/* event= */ "cleanUpRecentsAnimation");
         if (mLiveTileCleanUpHandler != null) {
             mLiveTileCleanUpHandler.run();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
index 5c5b9ca..7a2b343 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -18,6 +18,8 @@
 
 import android.content.Context;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.R;
 import com.android.launcher3.util.ResourceBasedOverride;
 
@@ -33,12 +35,15 @@
     }
 
     /**
-     * Called when nav handle is long pressed.
-     *
-     * @return if the long press was consumed, meaning other input consumers should receive a
-     * cancel event
+     * Called when nav handle is long pressed to get the Runnable that should be executed by the
+     * caller to invoke long press behavior. If null is returned that means long press couldn't be
+     * handled.
+     * <p>
+     * A Runnable is returned here to ensure the InputConsumer can call
+     * {@link android.view.InputMonitor#pilferPointers()} before invoking the long press behavior
+     * since pilfering can break the long press behavior.
      */
-    public boolean onLongPress() {
-        return false;
+    public @Nullable Runnable getLongPressRunnable() {
+        return null;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index 542dea1..a9accb7 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -38,8 +38,8 @@
     public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor) {
         super(delegate, inputMonitor);
-        mNavHandleWidth = context.getResources()
-                .getDimensionPixelSize(R.dimen.navigation_home_handle_width);
+        mNavHandleWidth = context.getResources().getDimensionPixelSize(
+                R.dimen.navigation_home_handle_width);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
 
         mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);
@@ -48,8 +48,11 @@
             @Override
             public void onLongPress(MotionEvent motionEvent) {
                 if (isInArea(motionEvent.getRawX())) {
-                    if (mNavHandleLongPressHandler.onLongPress()) {
+                    Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
+                    if (longPressRunnable != null) {
                         setActive(motionEvent);
+
+                        longPressRunnable.run();
                     }
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index 5f3fd0c..dfd4390 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -109,9 +109,17 @@
     public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
 
-        mSnapshotDrawParams = new FullscreenDrawParams(
-                QuickStepContract.getWindowCornerRadius(context),
-                QuickStepContract.getWindowCornerRadius(context));
+        mSnapshotDrawParams = new FullscreenDrawParams(context) {
+            @Override
+            public float computeTaskCornerRadius(Context context) {
+                return QuickStepContract.getWindowCornerRadius(context);
+            }
+
+            @Override
+            public float computeWindowCornerRadius(Context context) {
+                return QuickStepContract.getWindowCornerRadius(context);
+            }
+        };
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index be9da34..76f9c2c 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4794,8 +4794,9 @@
                         } else {
                             resetFromSplitSelectionState();
                         }
+                        InteractionJankMonitorWrapper.end(
+                                InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
                     });
-            InteractionJankMonitorWrapper.end(InteractionJankMonitorWrapper.CUJ_SPLIT_SCREEN_ENTER);
         });
 
         mSecondSplitHiddenView = containerTaskView;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 854c3c7..a2976a8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -71,6 +71,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.DeviceProfile;
@@ -133,15 +134,17 @@
 
     public static final int FLAG_UPDATE_ICON = 1;
     public static final int FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON << 1;
+    public static final int FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL << 1;
 
-    public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL;
+    public static final int FLAG_UPDATE_ALL = FLAG_UPDATE_ICON | FLAG_UPDATE_THUMBNAIL
+            | FLAG_UPDATE_CORNER_RADIUS;
 
     /**
      * Used in conjunction with {@link #onTaskListVisibilityChanged(boolean, int)}, providing more
      * granularity on which components of this task require an update
      */
     @Retention(SOURCE)
-    @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
+    @IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS})
     public @interface TaskDataChanges {}
 
     /**
@@ -1079,6 +1082,9 @@
                             mDigitalWellBeingToast.initialize(task);
                         });
             }
+            if (needsUpdate(changes, FLAG_UPDATE_CORNER_RADIUS)) {
+                mCurrentFullscreenParams.updateCornerRadius(getContext());
+            }
         } else {
             if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
                 mSnapshotView.setThumbnail(null, null);
@@ -1859,19 +1865,29 @@
      */
     public static class FullscreenDrawParams {
 
-        private final float mCornerRadius;
-        private final float mWindowCornerRadius;
+        private float mCornerRadius;
+        private float mWindowCornerRadius;
 
         public float mCurrentDrawnCornerRadius;
 
         public FullscreenDrawParams(Context context) {
-            this(TaskCornerRadius.get(context), QuickStepContract.getWindowCornerRadius(context));
+            updateCornerRadius(context);
         }
 
-        FullscreenDrawParams(float cornerRadius, float windowCornerRadius) {
-            mCornerRadius = cornerRadius;
-            mWindowCornerRadius = windowCornerRadius;
-            mCurrentDrawnCornerRadius = mCornerRadius;
+        /** Recomputes the start and end corner radius for the given Context. */
+        public void updateCornerRadius(Context context) {
+            mCornerRadius = computeTaskCornerRadius(context);
+            mWindowCornerRadius = computeWindowCornerRadius(context);
+        }
+
+        @VisibleForTesting
+        public float computeTaskCornerRadius(Context context) {
+            return TaskCornerRadius.get(context);
+        }
+
+        @VisibleForTesting
+        public float computeWindowCornerRadius(Context context) {
+            return QuickStepContract.getWindowCornerRadius(context);
         }
 
         /**
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
index a9dc043..bb1afdf 100644
--- a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep
 
+import android.content.Context
 import android.graphics.Rect
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -29,7 +30,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
 
 /** Test for FullscreenDrawParams class. */
 @SmallTest
@@ -186,4 +189,76 @@
         val expectedRadius = QuickStepContract.getWindowCornerRadius(context)
         assertThat(params.mCurrentDrawnCornerRadius).isEqualTo(expectedRadius)
     }
+
+    @Test
+    fun setStartProgress_correctCornerRadiusForMultiDisplay() {
+        val display1Context = context
+        val display2Context = mock(Context::class.java)
+        val spyParams = spy(params)
+
+        val display1TaskRadius = TaskCornerRadius.get(display1Context)
+        val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
+        val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
+        val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
+        doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
+        doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+
+        spyParams.updateCornerRadius(display1Context)
+        spyParams.setProgress(
+            /* fullscreenProgress= */ 0f,
+            /* parentScale= */ 1.0f,
+            /* taskViewScale= */ 1.0f,
+            /* unused previewWidth= */ -1,
+            /* unusedDp= */ null,
+            /* unused previewPositionHelper= */ null
+        )
+        assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1TaskRadius)
+
+        spyParams.updateCornerRadius(display2Context)
+        spyParams.setProgress(
+            /* fullscreenProgress= */ 0f,
+            /* parentScale= */ 1.0f,
+            /* taskViewScale= */ 1.0f,
+            /* unused previewWidth= */ -1,
+            /* unusedDp= */ null,
+            /* unused previewPositionHelper= */ null
+        )
+        assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2TaskRadius)
+    }
+
+    @Test
+    fun setFullProgress_correctCornerRadiusForMultiDisplay() {
+        val display1Context = context
+        val display2Context = mock(Context::class.java)
+        val spyParams = spy(params)
+
+        val display1TaskRadius = TaskCornerRadius.get(display1Context)
+        val display1WindowRadius = QuickStepContract.getWindowCornerRadius(display1Context)
+        val display2TaskRadius = display1TaskRadius * 2 + 1 // Arbitrarily different.
+        val display2WindowRadius = display1WindowRadius * 2 + 1 // Arbitrarily different.
+        doReturn(display2TaskRadius).`when`(spyParams).computeTaskCornerRadius(display2Context)
+        doReturn(display2WindowRadius).`when`(spyParams).computeWindowCornerRadius(display2Context)
+
+        spyParams.updateCornerRadius(display1Context)
+        spyParams.setProgress(
+            /* fullscreenProgress= */ 1.0f,
+            /* parentScale= */ 1.0f,
+            /* taskViewScale= */ 1.0f,
+            /* unused previewWidth= */ -1,
+            /* unusedDp= */ null,
+            /* unused previewPositionHelper= */ null
+        )
+        assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display1WindowRadius)
+
+        spyParams.updateCornerRadius(display2Context)
+        spyParams.setProgress(
+            /* fullscreenProgress= */ 1.0f,
+            /* parentScale= */ 1.0f,
+            /* taskViewScale= */ 1.0f,
+            /* unused previewWidth= */ -1,
+            /* unusedDp= */ null,
+            /* unused previewPositionHelper= */ null
+        )
+        assertThat(spyParams.mCurrentDrawnCornerRadius).isEqualTo(display2WindowRadius)
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 40d0ac7..25f90ca 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -185,7 +185,6 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @ScreenRecord // b/195673272
     @PlatinumTest(focusArea = "launcher")
     public void testOverviewActions() throws Exception {
         // Experimenting for b/165029151:
diff --git a/src/com/android/launcher3/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 3c90408..8ec5c18 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -74,14 +74,9 @@
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         // If this is a back key, propagate the key back to the listener
-        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
-            if (TextUtils.isEmpty(getText())) {
-                hideKeyboard();
-            }
-            if (mBackKeyListener != null) {
-                return mBackKeyListener.onBackKey();
-            }
-            return false;
+        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP
+                && mBackKeyListener != null) {
+            return mBackKeyListener.onBackKey();
         }
         return super.onKeyPreIme(keyCode, event);
     }
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 4768773..c6c38fc 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -55,6 +55,7 @@
         int y = presenterPos.cellY;
         if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT
                 || info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+            Log.d(TAG, "add predicted icon " + child.getTag().toString() + " to home screen");
             int screenId = presenterPos.screenId;
             x = getHotseat().getCellXFromOrder(screenId);
             y = getHotseat().getCellYFromOrder(screenId);
diff --git a/src/com/android/launcher3/testing/TestLogging.java b/src/com/android/launcher3/testing/TestLogging.java
index f95548d..3db2ddf 100644
--- a/src/com/android/launcher3/testing/TestLogging.java
+++ b/src/com/android/launcher3/testing/TestLogging.java
@@ -27,26 +27,29 @@
 import java.util.function.BiConsumer;
 
 public final class TestLogging {
+    private static final String TAPL_EVENTS_TAG = "TaplEvents";
+    private static final String LAUNCHER_EVENTS_TAG = "LauncherEvents";
     private static BiConsumer<String, String> sEventConsumer;
     public static boolean sHadEventsNotFromTest;
 
-    private static void recordEventSlow(String sequence, String event) {
-        Log.d(TestProtocol.TAPL_EVENTS_TAG, sequence + " / " + event);
+    private static void recordEventSlow(String sequence, String event, boolean reportToTapl) {
+        Log.d(reportToTapl ? TAPL_EVENTS_TAG : LAUNCHER_EVENTS_TAG,
+                sequence + " / " + event);
         final BiConsumer<String, String> eventConsumer = sEventConsumer;
-        if (eventConsumer != null) {
+        if (reportToTapl && eventConsumer != null) {
             eventConsumer.accept(sequence, event);
         }
     }
 
     public static void recordEvent(String sequence, String event) {
         if (Utilities.isRunningInTestHarness()) {
-            recordEventSlow(sequence, event);
+            recordEventSlow(sequence, event, true);
         }
     }
 
     public static void recordEvent(String sequence, String message, Object parameter) {
         if (Utilities.isRunningInTestHarness()) {
-            recordEventSlow(sequence, message + ": " + parameter);
+            recordEventSlow(sequence, message + ": " + parameter, true);
         }
     }
 
@@ -59,14 +62,23 @@
 
     public static void recordKeyEvent(String sequence, String message, KeyEvent event) {
         if (Utilities.isRunningInTestHarness()) {
-            recordEventSlow(sequence, message + ": " + event);
+            recordEventSlow(sequence, message + ": " + event, true);
             registerEventNotFromTest(event);
         }
     }
 
     public static void recordMotionEvent(String sequence, String message, MotionEvent event) {
-        if (Utilities.isRunningInTestHarness() && event.getAction() != MotionEvent.ACTION_MOVE) {
-            recordEventSlow(sequence, message + ": " + event);
+        final int action = event.getAction();
+        if (Utilities.isRunningInTestHarness() && action != MotionEvent.ACTION_MOVE) {
+            // "Expecting" in TAPL ACTION_DOWN, UP and CANCEL events was thought to be producing
+            // considerable noise in tests due to failed checks for expected events. So we are not
+            // sending them to TAPL.
+            // Other events, such as EVENT_PILFER_POINTERS produce less noise and are thought to
+            // be more useful.
+            final boolean reportToTapl = action != MotionEvent.ACTION_DOWN
+                    && action != MotionEvent.ACTION_UP
+                    && action != MotionEvent.ACTION_CANCEL;
+            recordEventSlow(sequence, message + ": " + event, reportToTapl);
             registerEventNotFromTest(event);
         }
     }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index abca1f8..a4b605c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -794,13 +794,15 @@
         }
 
         // Checks the orientation of the screen
-        if (LARGE_SCREEN_WIDGET_PICKER.get()
-                && mOrientation != newConfig.orientation
-                && mDeviceProfile.isTablet
-                && !mDeviceProfile.isTwoPanels) {
+        if (mOrientation != newConfig.orientation) {
             mOrientation = newConfig.orientation;
-            handleClose(false);
-            show(Launcher.getLauncher(getContext()), false);
+            if (LARGE_SCREEN_WIDGET_PICKER.get()
+                    && mDeviceProfile.isTablet && !mDeviceProfile.isTwoPanels) {
+                handleClose(false);
+                show(Launcher.getLauncher(getContext()), false);
+            } else {
+                reset();
+            }
         }
     }
 
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index bb61fbe..ee151bb 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -345,6 +345,15 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity-alias android:name="WebSearchActivity"
+            android:label="WebSearchActivity"
+            android:exported="true"
+            android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.WEB_SEARCH" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity-alias>
 
         <!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
         <provider
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index 87ec260..30732a9 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -39,7 +39,6 @@
     public static final int HINT_STATE_TWO_BUTTON_ORDINAL = 8;
     public static final int OVERVIEW_SPLIT_SELECT_ORDINAL = 9;
     public static final int EDIT_MODE_STATE_ORDINAL = 10;
-    public static final String TAPL_EVENTS_TAG = "TaplEvents";
     public static final String SEQUENCE_MAIN = "Main";
     public static final String SEQUENCE_TIS = "TIS";
     public static final String SEQUENCE_PILFER = "Pilfer";
@@ -155,7 +154,6 @@
 
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String TWO_TASKBAR_LONG_CLICKS = "b/262282528";
-    public static final String FLAKY_ACTIVITY_COUNT = "b/260260325";
     public static final String FLAKY_QUICK_SWITCH_TO_PREVIOUS_APP = "b/286084688";
     public static final String ICON_MISSING = "b/282963545";
     public static final String LAUNCH_SPLIT_PAIR = "b/288939273";
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index e12cf2d..45b01f4 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -158,7 +158,6 @@
     }
 
     @Test
-    @ScreenRecord
     public void testPressHomeOnAllAppsContextMenu() throws Exception {
         final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
         allApps.freeze();
@@ -556,8 +555,9 @@
 
     @Test
     @PortraitLandscape
-    @PlatinumTest(focusArea = "launcher")
-    @ScreenRecord // TODO(b/293944634): Remove after flaky debug
+    // TODO(b/293944634): Remove Screenrecord after flaky debug, and add
+    // @PlatinumTest(focusArea = "launcher") back
+    @ScreenRecord
     public void testUninstallFromWorkspace() throws Exception {
         installDummyAppAndWaitForUIUpdate();
         try {
@@ -619,10 +619,13 @@
         }
     }
 
+    /**
+     * Adds three icons to the workspace and removes one of them by dragging to uninstall.
+     */
     @Test
     @ScreenRecord // b/241821721
     @PlatinumTest(focusArea = "launcher")
-    public void getIconsPosition_afterIconRemoved_notContained() throws IOException {
+    public void uninstallWorkspaceIcon() throws IOException {
         Point[] gridPositions = getCornersAndCenterPositions();
         StringBuilder sb = new StringBuilder();
         for (Point p : gridPositions) {
@@ -643,6 +646,10 @@
             mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
                     DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
+            // Debug for b/288944469 I want to test if we are not waiting enough after removing
+            // the icon to request the list of icons again, since the items are not removed
+            // immediately. This should reduce the flake rate
+            SystemClock.sleep(500);
             Map<String, Point> finalPositions =
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
             assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
index 49abad4..4b65439 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AlphaJumpDetector.java
@@ -180,7 +180,8 @@
     }
 
     @Override
-    String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp) {
+    String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN, long timestamp,
+            int windowSizePx) {
         // If the view was previously seen, proceed with analysis only if it was present in the
         // view hierarchy in the previous frame.
         if (oldInfo != null && oldInfo.frameN != frameN) return null;
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java
index 09e2f65..786791c 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/AnomalyDetector.java
@@ -68,17 +68,18 @@
      * null value means that the view. 'oldInfo' and 'newInfo' cannot be both null.
      * If an anomaly is detected, an exception will be thrown.
      *
-     * @param oldInfo the view, as seen in the last frame that contained it in the view
-     *                hierarchy before 'currentFrame'. 'null' means that the view is first seen
-     *                in the 'currentFrame'.
-     * @param newInfo the view in the view hierarchy of the 'currentFrame'. 'null' means that
-     *                the view is not present in the 'currentFrame', but was present in the previous
-     *                frame.
-     * @param frameN  number of the current frame.
+     * @param oldInfo      the view, as seen in the last frame that contained it in the view
+     *                     hierarchy before 'currentFrame'. 'null' means that the view is first seen
+     *                     in the 'currentFrame'.
+     * @param newInfo      the view in the view hierarchy of the 'currentFrame'. 'null' means that
+     *                     the view is not present in the 'currentFrame', but was present in the
+     *                     previous frame.
+     * @param frameN       number of the current frame.
+     * @param windowSizePx maximum of the window width and height, in pixels.
      * @return Anomaly diagnostic message if an anomaly has been detected; null otherwise.
      */
     abstract String detectAnomalies(
             @Nullable ViewCaptureAnalyzer.AnalysisNode oldInfo,
             @Nullable ViewCaptureAnalyzer.AnalysisNode newInfo, int frameN,
-            long frameTimeNs);
+            long frameTimeNs, int windowSizePx);
 }
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
index 6d9198f..8b88ace 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/FlashDetector.java
@@ -110,7 +110,7 @@
 
     @Override
     String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN,
-            long frameTimeNs) {
+            long frameTimeNs, int windowSizePx) {
         // Should we check when a view was visible for a short period, then its alpha became 0?
         // Then 'lastVisible' time should be the last one still visible?
         // Check only transitions of alpha between 0 and 1?
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java
new file mode 100644
index 0000000..a1ddcb0
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/PositionJumpDetector.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 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.util.viewcapture_analysis;
+
+import com.android.launcher3.util.viewcapture_analysis.ViewCaptureAnalyzer.AnalysisNode;
+
+import java.util.List;
+
+/**
+ * Anomaly detector that triggers an error when a view position jumps.
+ */
+final class PositionJumpDetector extends AnomalyDetector {
+    // Maximum allowed jump in "milliwindows", i.e. a 1/1000's of the maximum of the window
+    // dimensions.
+    private static final float JUMP_MIW = 250;
+
+    private static final String[] BORDER_NAMES = {"left", "top", "right", "bottom"};
+
+    // Commonly used parts of the paths to ignore.
+    private static final String CONTENT = "DecorView|LinearLayout|FrameLayout:id/content|";
+    private static final String DRAG_LAYER =
+            CONTENT + "LauncherRootView:id/launcher|DragLayer:id/drag_layer|";
+    private static final String RECENTS_DRAG_LAYER =
+            CONTENT + "LauncherRootView:id/launcher|RecentsDragLayer:id/drag_layer|";
+
+    private static final IgnoreNode IGNORED_NODES_ROOT = buildIgnoreNodesTree(List.of(
+            DRAG_LAYER + "SearchContainerView:id/apps_view",
+            DRAG_LAYER + "AppWidgetResizeFrame",
+            DRAG_LAYER + "LauncherAllAppsContainerView:id/apps_view",
+            CONTENT
+                    + "AddItemDragLayer:id/add_item_drag_layer|AddItemWidgetsBottomSheet:id"
+                    + "/add_item_bottom_sheet|LinearLayout:id/add_item_bottom_sheet_content",
+            DRAG_LAYER + "WidgetsTwoPaneSheet|SpringRelativeLayout:id/container",
+            DRAG_LAYER + "WidgetsFullSheet|SpringRelativeLayout:id/container",
+            DRAG_LAYER + "LauncherDragView",
+            RECENTS_DRAG_LAYER + "FallbackRecentsView:id/overview_panel|TaskView",
+            CONTENT + "LauncherRootView:id/launcher|FloatingIconView",
+            DRAG_LAYER + "FloatingTaskView",
+            DRAG_LAYER + "LauncherRecentsView:id/overview_panel"
+    ));
+
+    // Per-AnalysisNode data that's specific to this detector.
+    private static class NodeData {
+        public boolean ignoreJumps;
+
+        // If ignoreNode is null, then this AnalysisNode node will be ignored if its parent is
+        // ignored.
+        // Otherwise, this AnalysisNode will be ignored if ignoreNode is a leaf i.e. has no
+        // children.
+        public IgnoreNode ignoreNode;
+    }
+
+    private NodeData getNodeData(AnalysisNode info) {
+        return (NodeData) info.detectorsData[detectorOrdinal];
+    }
+
+    @Override
+    void initializeNode(AnalysisNode info) {
+        final NodeData nodeData = new NodeData();
+        info.detectorsData[detectorOrdinal] = nodeData;
+
+        // If the parent view ignores jumps, its descendants will too.
+        final boolean parentIgnoresJumps = info.parent != null && getNodeData(
+                info.parent).ignoreJumps;
+        if (parentIgnoresJumps) {
+            nodeData.ignoreJumps = true;
+            return;
+        }
+
+        // Parent view doesn't ignore jumps.
+        // Initialize this AnalysisNode's ignore-node with the corresponding child of the
+        // ignore-node of the parent, if present.
+        final IgnoreNode parentIgnoreNode = info.parent != null
+                ? getNodeData(info.parent).ignoreNode
+                : IGNORED_NODES_ROOT;
+        nodeData.ignoreNode = parentIgnoreNode != null
+                ? parentIgnoreNode.children.get(info.nodeIdentity) : null;
+        // AnalysisNode will be ignored if the corresponding ignore-node is a leaf.
+        nodeData.ignoreJumps =
+                nodeData.ignoreNode != null && nodeData.ignoreNode.children.isEmpty();
+    }
+
+    @Override
+    String detectAnomalies(AnalysisNode oldInfo, AnalysisNode newInfo, int frameN,
+            long frameTimeNs, int windowSizePx) {
+        // If the view is not present in the current frame, there can't be a jump detected in the
+        // current frame.
+        if (newInfo == null) return null;
+
+        // We only detect position jumps if the view was visible in the previous frame.
+        if (oldInfo == null || frameN != oldInfo.frameN + 1) return null;
+
+        final NodeData newNodeData = getNodeData(newInfo);
+        if (newNodeData.ignoreJumps) return null;
+
+        final float[] positionDiffs = {
+                newInfo.left - oldInfo.left,
+                newInfo.top - oldInfo.top,
+                newInfo.right - oldInfo.right,
+                newInfo.bottom - oldInfo.bottom
+        };
+
+        for (int i = 0; i < 4; ++i) {
+            final float positionDiffAbs = Math.abs(positionDiffs[i]);
+            if (positionDiffAbs * 1000 > JUMP_MIW * windowSizePx) {
+                newNodeData.ignoreJumps = true;
+                return String.format("Position jump: %s jumped by %s",
+                        BORDER_NAMES[i], positionDiffAbs);
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
index ccb4a1e..9459cc2 100644
--- a/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
+++ b/tests/src/com/android/launcher3/util/viewcapture_analysis/ViewCaptureAnalyzer.java
@@ -36,7 +36,8 @@
     // All detectors. They will be invoked in the order listed here.
     private static final AnomalyDetector[] ANOMALY_DETECTORS = {
             new AlphaJumpDetector(),
-            new FlashDetector()
+            new FlashDetector(),
+            new PositionJumpDetector()
     };
 
     static {
@@ -52,6 +53,8 @@
         // Window coordinates of the view.
         public float left;
         public float top;
+        public float right;
+        public float bottom;
 
         // Visible scale and alpha, build recursively from the ancestor list.
         public float scaleX;
@@ -81,7 +84,8 @@
 
         @Override
         public String toString() {
-            return String.format("view window coordinates: (%s, %s)", left, top);
+            return String.format("view window coordinates: (%s, %s, %s, %s)",
+                    left, top, right, bottom);
         }
     }
 
@@ -112,15 +116,33 @@
         // As we go though frames, if a view becomes invisible, it stays in the map.
         final Map<Integer, AnalysisNode> lastSeenNodes = new HashMap<>();
 
+        int windowWidthPx = -1;
+        int windowHeightPx = -1;
+
         for (int frameN = 0; frameN < windowData.getFrameDataCount(); ++frameN) {
-            analyzeFrame(frameN, windowData.getFrameData(frameN), viewCaptureData, lastSeenNodes,
-                    scrimClassIndex, anomalies);
+            final FrameData frame = windowData.getFrameData(frameN);
+            final ViewNode rootNode = frame.getNode();
+
+            // If the rotation or window size has changed, reset the analyzer state.
+            final boolean isFirstFrame = windowWidthPx != rootNode.getWidth()
+                    || windowHeightPx != rootNode.getHeight();
+            if (isFirstFrame) {
+                windowWidthPx = rootNode.getWidth();
+                windowHeightPx = rootNode.getHeight();
+                lastSeenNodes.clear();
+            }
+
+            final int windowSizePx = Math.max(rootNode.getWidth(), rootNode.getHeight());
+
+            analyzeFrame(frameN, isFirstFrame, frame, viewCaptureData, lastSeenNodes,
+                    scrimClassIndex, anomalies, windowSizePx);
         }
     }
 
-    private static void analyzeFrame(int frameN, FrameData frame, ExportedData viewCaptureData,
+    private static void analyzeFrame(int frameN, boolean isFirstFrame, FrameData frame,
+            ExportedData viewCaptureData,
             Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
-            Map<String, String> anomalies) {
+            Map<String, String> anomalies, int windowSizePx) {
         // Analyze the node tree starting from the root.
         long frameTimeNs = frame.getTimestamp();
         analyzeView(
@@ -128,12 +150,14 @@
                 frame.getNode(),
                 /* parent = */ null,
                 frameN,
+                isFirstFrame,
                 /* leftShift = */ 0,
                 /* topShift = */ 0,
                 viewCaptureData,
                 lastSeenNodes,
                 scrimClassIndex,
-                anomalies);
+                anomalies,
+                windowSizePx);
 
         // Analyze transitions when a view visible in the previous frame became invisible in the
         // current one.
@@ -148,7 +172,8 @@
                                             /* oldInfo = */ info,
                                             /* newInfo = */ null,
                                             anomalies,
-                                            frameTimeNs)
+                                            frameTimeNs,
+                                            windowSizePx)
                     );
                 }
                 info.timeBecameInvisibleNs = info.alpha == 1 ? frameTimeNs : -1;
@@ -159,9 +184,9 @@
 
     private static void analyzeView(long frameTimeNs, ViewNode viewCaptureNode, AnalysisNode parent,
             int frameN,
-            float leftShift, float topShift, ExportedData viewCaptureData,
+            boolean isFirstFrame, float leftShift, float topShift, ExportedData viewCaptureData,
             Map<Integer, AnalysisNode> lastSeenNodes, int scrimClassIndex,
-            Map<String, String> anomalies) {
+            Map<String, String> anomalies, int windowSizePx) {
         // Skip analysis of invisible views
         final float parentAlpha = parent != null ? parent.alpha : 1;
         final float alpha = getVisibleAlpha(viewCaptureNode, parentAlpha);
@@ -182,6 +207,8 @@
         final float top = topShift
                 + (viewCaptureNode.getTop() + viewCaptureNode.getTranslationY()) * parentScaleY
                 + viewCaptureNode.getHeight() * (parentScaleY - scaleY) / 2;
+        final float width = viewCaptureNode.getWidth() * scaleX;
+        final float height = viewCaptureNode.getHeight() * scaleY;
 
         // Initialize new analysis node
         final AnalysisNode newAnalysisNode = new AnalysisNode();
@@ -192,6 +219,8 @@
         newAnalysisNode.parent = parent;
         newAnalysisNode.left = left;
         newAnalysisNode.top = top;
+        newAnalysisNode.right = left + width;
+        newAnalysisNode.bottom = top + height;
         newAnalysisNode.scaleX = scaleX;
         newAnalysisNode.scaleY = scaleY;
         newAnalysisNode.alpha = alpha;
@@ -216,11 +245,11 @@
         }
 
         // Detect anomalies for the view.
-        if (frameN != 0 && !viewCaptureNode.getWillNotDraw()) {
+        if (!isFirstFrame && !viewCaptureNode.getWillNotDraw()) {
             Arrays.stream(ANOMALY_DETECTORS).forEach(
                     detector ->
                             detectAnomaly(detector, frameN, oldAnalysisNode, newAnalysisNode,
-                                    anomalies, frameTimeNs)
+                                    anomalies, frameTimeNs, windowSizePx)
             );
         }
         lastSeenNodes.put(hashcode, newAnalysisNode);
@@ -235,17 +264,19 @@
             // transparent.
             if (child.getClassnameIndex() == scrimClassIndex) break;
 
-            analyzeView(frameTimeNs, child, newAnalysisNode, frameN, leftShiftForChildren,
+            analyzeView(frameTimeNs, child, newAnalysisNode, frameN, isFirstFrame,
+                    leftShiftForChildren,
                     topShiftForChildren,
-                    viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies);
+                    viewCaptureData, lastSeenNodes, scrimClassIndex, anomalies, windowSizePx);
         }
     }
 
     private static void detectAnomaly(AnomalyDetector detector, int frameN,
             AnalysisNode oldAnalysisNode, AnalysisNode newAnalysisNode,
-            Map<String, String> anomalies, long frameTimeNs) {
+            Map<String, String> anomalies, long frameTimeNs, int windowSizePx) {
         final String maybeAnomaly =
-                detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs);
+                detector.detectAnomalies(oldAnalysisNode, newAnalysisNode, frameN, frameTimeNs,
+                        windowSizePx);
         if (maybeAnomaly != null) {
             AnalysisNode latestInfo = newAnalysisNode != null ? newAnalysisNode : oldAnalysisNode;
             final String viewDiagPath = diagPathFromRoot(latestInfo);
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 7dd5827..ebcca00 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -18,8 +18,6 @@
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
-import static com.android.launcher3.tapl.LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS;
-import static com.android.launcher3.tapl.LauncherInstrumentation.EVENT_TOUCH_UP_TIS;
 import static com.android.launcher3.tapl.OverviewTask.TASK_START_EVENT;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 
@@ -133,16 +131,6 @@
                 }
             }
         } else {
-            if (mLauncher.isTablet()) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                        LauncherInstrumentation.EVENT_TOUCH_DOWN);
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                        LauncherInstrumentation.EVENT_TOUCH_UP);
-            }
-            if (mLauncher.isTrackpadGestureEnabled()) {
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-            }
             mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
             mLauncher.runToState(
                     () -> mLauncher.waitForNavigationUiObject("recent_apps").click(),
@@ -203,7 +191,7 @@
 
         final LauncherInstrumentation.GestureScope gestureScope =
                 zeroButtonToOverviewGestureStartsInLauncher()
-                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
+                        ? LauncherInstrumentation.GestureScope.INSIDE
                         : LauncherInstrumentation.GestureScope.OUTSIDE_WITHOUT_PILFER;
 
         mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
@@ -273,30 +261,10 @@
             } else {
                 // Double press the recents button.
                 UiObject2 recentsButton = mLauncher.waitForNavigationUiObject("recent_apps");
-                if (mLauncher.isTablet()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_DOWN);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_UP);
-                }
-                if (mLauncher.isTrackpadGestureEnabled()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-                }
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL,
                         "clicking Recents button for the first time");
                 mLauncher.getOverview();
-                if (mLauncher.isTablet()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_DOWN);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            LauncherInstrumentation.EVENT_TOUCH_UP);
-                }
-                if (mLauncher.isTrackpadGestureEnabled()) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-                }
                 mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, SQUARE_BUTTON_EVENT);
                 mLauncher.executeAndWaitForEvent(
                         () -> recentsButton.click(),
diff --git a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
index 9a7710a..230be06 100644
--- a/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
+++ b/tests/tapl/com/android/launcher3/tapl/LaunchedAppState.java
@@ -194,7 +194,7 @@
                             SystemClock.uptimeMillis(),
                             MotionEvent.ACTION_UP,
                             endPoint,
-                            LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER);
+                            LauncherInstrumentation.GestureScope.INSIDE);
                     LauncherInstrumentation.log("SplitscreenDragSource.dragToSplitscreen: "
                             + "after drop");
 
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 262d5ff..e6fc244 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -99,17 +99,9 @@
     private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 15;
     private static final int GESTURE_STEP_MS = 16;
 
-    static final Pattern EVENT_TOUCH_DOWN = getTouchEventPatternWithPointerCount("ACTION_DOWN");
-    static final Pattern EVENT_TOUCH_UP = getTouchEventPatternWithPointerCount("ACTION_UP");
-    private static final Pattern EVENT_TOUCH_CANCEL = getTouchEventPatternWithPointerCount(
-            "ACTION_CANCEL");
     static final Pattern EVENT_PILFER_POINTERS = Pattern.compile("pilferPointers");
     static final Pattern EVENT_START = Pattern.compile("start:");
 
-    static final Pattern EVENT_TOUCH_DOWN_TIS = getTouchEventPatternTIS("ACTION_DOWN");
-    static final Pattern EVENT_TOUCH_UP_TIS = getTouchEventPatternTIS("ACTION_UP");
-    static final Pattern EVENT_TOUCH_CANCEL_TIS = getTouchEventPattern(
-            "TouchInteractionService.onInputEvent", "ACTION_CANCEL");
     static final Pattern EVENT_HOVER_ENTER_TIS = getTouchEventPatternTIS("ACTION_HOVER_ENTER");
     static final Pattern EVENT_HOVER_EXIT_TIS = getTouchEventPatternTIS("ACTION_HOVER_EXIT");
     static final Pattern EVENT_BUTTON_PRESS_TIS = getTouchEventPatternTIS("ACTION_BUTTON_PRESS");
@@ -139,7 +131,6 @@
     // whether the gesture recognition triggers pilfer.
     public enum GestureScope {
         OUTSIDE_WITHOUT_PILFER, OUTSIDE_WITH_PILFER, INSIDE, INSIDE_TO_OUTSIDE,
-        INSIDE_TO_OUTSIDE_WITHOUT_PILFER,
         INSIDE_TO_OUTSIDE_WITH_KEYCODE, // For gestures that will trigger a keycode from TIS.
         OUTSIDE_WITH_KEYCODE,
     }
@@ -213,12 +204,6 @@
     private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
     private int mPointerCount = 0;
 
-    private static Pattern getTouchEventPattern(String prefix, String action) {
-        return Pattern.compile(
-                prefix + ": MotionEvent.*?action=" + action + ".*?id\\[0\\]=0"
-                        + ".*?toolType\\[0\\]=TOOL_TYPE_FINGER.*?buttonState=0.*?");
-    }
-
     private static Pattern getTouchEventPatternWithPointerCount(String prefix, String action,
             int pointerCount) {
         return Pattern.compile(
@@ -227,10 +212,6 @@
                         + pointerCount);
     }
 
-    private static Pattern getTouchEventPatternWithPointerCount(String action) {
-        return getTouchEventPatternWithPointerCount("Touch event", action, 1);
-    }
-
     private static Pattern getTouchEventPatternWithPointerCount(String action, int pointerCount) {
         return getTouchEventPatternWithPointerCount("Touch event", action, pointerCount);
     }
@@ -1072,14 +1053,6 @@
                 log("Hierarchy before clicking home:");
                 dumpViewHierarchy();
                 action = "clicking home button";
-                if (isTablet()) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
-                }
-                if (isTrackpadGestureEnabled()) {
-                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-                }
 
                 runToState(
                         waitForNavigationUiObject("home")::click,
@@ -1120,14 +1093,6 @@
                         10, false, gestureScope);
             } else {
                 waitForNavigationUiObject("back").click();
-                if (isTablet()) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_UP);
-                }
-                if (isTrackpadGestureEnabled()) {
-                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_UP_TIS);
-                }
             }
             if (launcherVisible) {
                 if (getContext().getApplicationInfo().isOnBackInvokedCallbackEnabled()) {
@@ -1781,44 +1746,17 @@
         boolean isTwoFingerTrackpadGesture = mTrackpadGestureType == TrackpadGestureType.TWO_FINGER;
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN:
-                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
-                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_TOUCH_DOWN);
-                }
-                if (hasTIS && (isTrackpadGestureEnabled()
-                        || getNavigationModel() != NavigationModel.THREE_BUTTON)) {
-                    expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_TOUCH_DOWN_TIS);
-                }
                 if (isTrackpadGesture) {
                     mPointerCount = 1;
                     pointerCount = mPointerCount;
                 }
                 break;
             case MotionEvent.ACTION_UP:
-                if (hasTIS && gestureScope != GestureScope.INSIDE
-                        && gestureScope != GestureScope.INSIDE_TO_OUTSIDE_WITHOUT_PILFER
+                if (hasTIS
                         && (gestureScope == GestureScope.OUTSIDE_WITH_PILFER
                         || gestureScope == GestureScope.INSIDE_TO_OUTSIDE)) {
                     expectEvent(TestProtocol.SEQUENCE_PILFER, EVENT_PILFER_POINTERS);
                 }
-                if (gestureScope != GestureScope.OUTSIDE_WITH_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITHOUT_PILFER
-                        && gestureScope != GestureScope.OUTSIDE_WITH_KEYCODE
-                        && (!isTrackpadGesture || isTwoFingerTrackpadGesture)) {
-                    expectEvent(TestProtocol.SEQUENCE_MAIN,
-                            gestureScope == GestureScope.INSIDE
-                                    || gestureScope == GestureScope.OUTSIDE_WITHOUT_PILFER
-                                    ? EVENT_TOUCH_UP : EVENT_TOUCH_CANCEL);
-                }
-                if (hasTIS && (isTrackpadGestureEnabled()
-                        || getNavigationModel() != NavigationModel.THREE_BUTTON)) {
-                    expectEvent(TestProtocol.SEQUENCE_TIS,
-                            gestureScope == GestureScope.INSIDE_TO_OUTSIDE_WITH_KEYCODE
-                                    || gestureScope == GestureScope.OUTSIDE_WITH_KEYCODE
-                                    ? EVENT_TOUCH_CANCEL_TIS : EVENT_TOUCH_UP_TIS);
-                }
                 break;
             case MotionEvent.ACTION_HOVER_ENTER:
                 expectEvent(TestProtocol.SEQUENCE_TIS, EVENT_HOVER_ENTER_TIS);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index 2f7596e..bd2c9c1 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -19,8 +19,6 @@
 import androidx.annotation.NonNull;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.testing.shared.TestProtocol;
-
 /**
  * View containing overview actions
  */
@@ -51,13 +49,6 @@
                     "clicked screenshot button")) {
                 UiObject2 closeScreenshot = mLauncher.waitForSystemUiObject(
                         "screenshot_dismiss_image");
-                if (mLauncher.isTrackpadGestureEnabled() || mLauncher.getNavigationModel()
-                        != LauncherInstrumentation.NavigationModel.THREE_BUTTON) {
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
-                            LauncherInstrumentation.EVENT_TOUCH_DOWN_TIS);
-                    mLauncher.expectEvent(TestProtocol.SEQUENCE_TIS,
-                            LauncherInstrumentation.EVENT_TOUCH_UP_TIS);
-                }
                 closeScreenshot.click();
                 try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
                         "dismissed screenshot")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 8c3402f..d02e747 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -59,21 +59,23 @@
     }
 
     /** Find the web suggestion from search suggestion's title text */
-    public void verifyWebSuggestIsPresent(String text) {
-        ArrayList<UiObject2> goldenGateResults =
+    public SearchWebSuggestion findWebSuggestion(String text) {
+        ArrayList<UiObject2> webSuggestions =
                 new ArrayList<>(mLauncher.waitForObjectsInContainer(
                         mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
                         By.clazz(TextView.class)));
-        boolean found = false;
-        for(UiObject2 uiObject: goldenGateResults) {
+        for (UiObject2 uiObject: webSuggestions) {
             String currentString = uiObject.getText();
             if (currentString.equals(text)) {
-                found = true;
+                return createWebSuggestion(uiObject);
             }
         }
-        if (!found) {
-            throw new IllegalStateException("Web suggestion title: " + text + " not found");
-        }
+        mLauncher.fail("Web suggestion title: " + text + " not found");
+        return null;
+    }
+
+    protected SearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
+        return new SearchWebSuggestion(mLauncher, webSuggestion);
     }
 
     /** Find the total amount of views being displayed and return the size */
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
index c267c9e..6c6ab05 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromTaskbarQsb.java
@@ -35,4 +35,14 @@
     protected TaskbarAppIcon createAppIcon(UiObject2 icon) {
         return new TaskbarAppIcon(mLauncher, icon);
     }
+
+    @Override
+    public TaskbarSearchWebSuggestion findWebSuggestion(String text) {
+        return (TaskbarSearchWebSuggestion) super.findWebSuggestion(text);
+    }
+
+    @Override
+    protected TaskbarSearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
+        return new TaskbarSearchWebSuggestion(mLauncher, webSuggestion);
+    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java
new file mode 100644
index 0000000..e4dec98
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/SearchWebSuggestion.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on a search web suggestion from a qsb.
+ */
+public class SearchWebSuggestion extends Launchable {
+
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onAllAppsItemLongClick");
+
+    SearchWebSuggestion(LauncherInstrumentation launcher, UiObject2 object) {
+        super(launcher, object);
+    }
+
+    @Override
+    protected void expectActivityStartEvents() {
+    }
+
+    @Override
+    protected String launchableType() {
+        return "search web suggestion";
+    }
+
+    @Override
+    protected void waitForLongPressConfirmation() {
+        mLauncher.waitForLauncherObject("popup_container");
+    }
+
+    @Override
+    protected void addExpectedEventsForLongClick() {
+        mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, getLongClickEvent());
+    }
+
+    protected Pattern getLongClickEvent() {
+        return LONG_CLICK_EVENT;
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
new file mode 100644
index 0000000..cd8ce42
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarSearchWebSuggestion.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.tapl;
+
+import androidx.test.uiautomator.UiObject2;
+
+import java.util.regex.Pattern;
+
+/**
+ * Operations on a search web suggestion from the Taskbar qsb.
+ */
+public class TaskbarSearchWebSuggestion extends SearchWebSuggestion implements
+        SplitscreenDragSource {
+
+    private static final Pattern LONG_CLICK_EVENT = Pattern.compile("onTaskbarItemLongClick");
+
+    TaskbarSearchWebSuggestion(LauncherInstrumentation launcher,
+            UiObject2 object) {
+        super(launcher, object);
+    }
+
+    @Override
+    protected Pattern getLongClickEvent() {
+        return LONG_CLICK_EVENT;
+    }
+
+    /** This method requires public access, however should not be called in tests. */
+    @Override
+    public Launchable getLaunchable() {
+        return this;
+    }
+}