Add a test base for AbsSwipeUpHandler and its subclasses

- Added a few basic tests for scenarios that should be contant across subclasses. These tests will not run on their own, they will only run when a subclass' test suite is run.
- Added test suites for LauncherSwipeHandlerV2 and FallbackSwipeHandler

Flag: EXEMPT adding tests
Bug: 353763639
Test: LauncherSwipeHandlerV2Test, FallbackSwipeHandlerTest
Change-Id: Ib44199f5d5281a544b433649d6ed49ae2a65ed70
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3d94442..bbf58a2 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -97,6 +97,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.jank.Cuj;
 import com.android.internal.util.LatencyTracker;
@@ -170,10 +171,12 @@
 /**
  * Handles the navigation gestures when Launcher is the default home activity.
  */
-public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
-        Q extends RecentsView, S extends BaseState<S>>
-        extends SwipeUpAnimationLogic implements OnApplyWindowInsetsListener,
-        RecentsAnimationCallbacks.RecentsAnimationListener {
+public abstract class AbsSwipeUpHandler<
+        RECENTS_CONTAINER extends Context & RecentsViewContainer,
+        RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
+        STATE extends BaseState<STATE>>
+        extends SwipeUpAnimationLogic
+        implements OnApplyWindowInsetsListener, RecentsAnimationCallbacks.RecentsAnimationListener {
     private static final String TAG = "AbsSwipeUpHandler";
 
     private static final ArrayList<String> STATE_NAMES = new ArrayList<>();
@@ -181,7 +184,7 @@
     // Fraction of the scroll and transform animation in which the current task fades out
     private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
 
-    protected final BaseContainerInterface<S, T> mContainerInterface;
+    protected final BaseContainerInterface<STATE, RECENTS_CONTAINER> mContainerInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
     protected final ActivityInitListener mActivityInitListener;
     // Callbacks to be made once the recents animation starts
@@ -192,8 +195,8 @@
     protected @Nullable RecentsAnimationController mRecentsAnimationController;
     protected @Nullable RecentsAnimationController mDeferredCleanupRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
-    protected @Nullable T mContainer;
-    protected @Nullable Q mRecentsView;
+    protected @Nullable RECENTS_CONTAINER mContainer;
+    protected @Nullable RECENTS_VIEW mRecentsView;
     protected Runnable mGestureEndCallback;
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
@@ -486,11 +489,11 @@
             return false;
         }
 
-        T createdContainer = (T) mContainerInterface.getCreatedContainer();
+        RECENTS_CONTAINER createdContainer = mContainerInterface.getCreatedContainer();
         if (createdContainer != null) {
             initTransitionEndpoints(createdContainer.getDeviceProfile());
         }
-        final T container = (T) mContainerInterface.getCreatedContainer();
+        final RECENTS_CONTAINER container = mContainerInterface.getCreatedContainer();
         if (mContainer == container) {
             return true;
         }
@@ -564,7 +567,7 @@
     }
 
     private void onLauncherStart() {
-        final T container = (T) mContainerInterface.getCreatedContainer();
+        final RECENTS_CONTAINER container = mContainerInterface.getCreatedContainer();
         if (container == null || mContainer != container) {
             return;
         }
@@ -1097,7 +1100,7 @@
      */
     @UiThread
     private void notifyGestureStarted() {
-        final T curActivity = mContainer;
+        final RECENTS_CONTAINER curActivity = mContainer;
         if (curActivity != null) {
             // Once the gesture starts, we can no longer transition home through the button, so
             // reset the force override of the activity visibility
@@ -1168,7 +1171,8 @@
     /**
      * Called if the end target has been set and the recents animation is started.
      */
-    private void onCalculateEndTarget() {
+    @VisibleForTesting
+    protected void onCalculateEndTarget() {
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
 
         switch (endTarget) {
@@ -1181,7 +1185,8 @@
         }
     }
 
-    private void onSettledOnEndTarget() {
+    @VisibleForTesting
+    protected void onSettledOnEndTarget() {
         // Fast-finish the attaching animation if it's still running.
         maybeUpdateRecentsAttachedState(false);
         final GestureEndTarget endTarget = mGestureState.getEndTarget();
@@ -1417,7 +1422,7 @@
             }
         }
         Interpolator interpolator;
-        S state = mContainerInterface.stateFromGestureEndTarget(endTarget);
+        STATE state = mContainerInterface.stateFromGestureEndTarget(endTarget);
         if (isKeyboardTaskFocusPending()) {
             interpolator = EMPHASIZED;
         } else if (state.displayOverviewTasksAsGrid(mDp)) {
@@ -2499,7 +2504,7 @@
     }
 
     private void animateSplashScreenExit(
-            @NonNull T activity,
+            @NonNull RECENTS_CONTAINER activity,
             @NonNull RemoteAnimationTarget[] appearedTaskTargets,
             @NonNull RemoteAnimationTarget[] animatingTargets) {
         ViewGroup splashView = activity.getDragLayer();
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index e17cdcd..d4b37f1 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -62,8 +62,8 @@
 /**
  * Temporary class to allow easier refactoring
  */
-public class LauncherSwipeHandlerV2 extends
-        AbsSwipeUpHandler<QuickstepLauncher, RecentsView, LauncherState> {
+public class LauncherSwipeHandlerV2 extends AbsSwipeUpHandler<
+        QuickstepLauncher, RecentsView<QuickstepLauncher, LauncherState>, LauncherState> {
 
     public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index f6b9e4e..d70f0d7 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -347,7 +347,7 @@
             taskAnimationManager.notifyRecentsAnimationState(recentAnimListener)
         } else {
             val intent =
-                Intent(interactionHandler.launchIntent)
+                Intent(interactionHandler.getLaunchIntent())
                     .putExtra(ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID, gestureState.gestureId)
             command.setAnimationCallbacks(
                 taskAnimationManager.startRecentsAnimation(gestureState, intent, interactionHandler)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
new file mode 100644
index 0000000..2a0aa4c
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2024 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.LauncherRootView;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.statemanager.BaseState;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.util.SystemUiController;
+import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.system.InputConsumerController;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.Collections;
+import java.util.HashMap;
+
+public abstract class AbsSwipeUpHandlerTestCase<
+        RECENTS_CONTAINER extends Context & RecentsViewContainer,
+        STATE extends BaseState<STATE>,
+        RECENTS_VIEW extends RecentsView<RECENTS_CONTAINER, STATE>,
+        ACTIVITY_TYPE extends  StatefulActivity<STATE> & RecentsViewContainer,
+        ACTIVITY_INTERFACE extends BaseActivityInterface<STATE, ACTIVITY_TYPE>,
+        SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE>> {
+
+    protected final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    protected final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(mContext);
+    protected final RecentsAnimationDeviceState mRecentsAnimationDeviceState =
+            new RecentsAnimationDeviceState(mContext, true);
+    protected final InputConsumerController mInputConsumerController =
+            InputConsumerController.getRecentsAnimationInputConsumer();
+    protected final ActivityManager.RunningTaskInfo mRunningTaskInfo =
+            new ActivityManager.RunningTaskInfo();
+    protected final TopTaskTracker.CachedTaskInfo mCachedTaskInfo =
+            new TopTaskTracker.CachedTaskInfo(Collections.singletonList(mRunningTaskInfo));
+    protected final RemoteAnimationTarget mRemoteAnimationTarget = new RemoteAnimationTarget(
+            /* taskId= */ 0,
+            /* mode= */ RemoteAnimationTarget.MODE_CLOSING,
+            /* leash= */ new SurfaceControl(),
+            /* isTranslucent= */ false,
+            /* clipRect= */ null,
+            /* contentInsets= */ null,
+            /* prefixOrderIndex= */ 0,
+            /* position= */ null,
+            /* localBounds= */ null,
+            /* screenSpaceBounds= */ null,
+            new Configuration().windowConfiguration,
+            /* isNotInRecents= */ false,
+            /* startLeash= */ null,
+            /* startBounds= */ null,
+            /* taskInfo= */ mRunningTaskInfo,
+            /* allowEnterPip= */ false);
+    protected final RecentsAnimationTargets mRecentsAnimationTargets = new RecentsAnimationTargets(
+            new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+            new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+            new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+            /* homeContentInsets= */ new Rect(),
+            /* minimizedHomeBounds= */ null,
+            new Bundle());
+
+    @Mock protected ACTIVITY_INTERFACE mActivityInterface;
+    @Mock protected ActivityInitListener<?> mActivityInitListener;
+    @Mock protected RecentsAnimationController mRecentsAnimationController;
+    @Mock protected STATE mState;
+    @Mock protected ViewTreeObserver mViewTreeObserver;
+    @Mock protected DragLayer mDragLayer;
+    @Mock protected LauncherRootView mRootView;
+    @Mock protected SystemUiController mSystemUiController;
+    @Mock protected GestureState mGestureState;
+
+    @Rule
+    public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Before
+    public void setUpRunningTaskInfo() {
+        mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+    }
+
+    @Before
+    public void setUpGestureState() {
+        when(mGestureState.getRunningTask()).thenReturn(mCachedTaskInfo);
+        when(mGestureState.getLastAppearedTaskIds()).thenReturn(new int[0]);
+        when(mGestureState.getLastStartedTaskIds()).thenReturn(new int[1]);
+        when(mGestureState.getHomeIntent()).thenReturn(new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        doReturn(mActivityInterface).when(mGestureState).getContainerInterface();
+    }
+
+    @Before
+    public void setUpRecentsView() {
+        RECENTS_VIEW recentsView = getRecentsView();
+        when(recentsView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
+        doAnswer(answer -> {
+            runOnMainSync(() -> answer.<Runnable>getArgument(0).run());
+            return this;
+        }).when(recentsView).runOnPageScrollsInitialized(any());
+    }
+
+    @Before
+    public void setUpRecentsContainer() {
+        RecentsViewContainer recentsContainer = getRecentsContainer();
+        RECENTS_VIEW recentsView = getRecentsView();
+
+        when(recentsContainer.getDeviceProfile()).thenReturn(new DeviceProfile());
+        when(recentsContainer.getOverviewPanel()).thenReturn(recentsView);
+        when(recentsContainer.getDragLayer()).thenReturn(mDragLayer);
+        when(recentsContainer.getRootView()).thenReturn(mRootView);
+        when(recentsContainer.getSystemUiController()).thenReturn(mSystemUiController);
+        when(mActivityInterface.createActivityInitListener(any()))
+                .thenReturn(mActivityInitListener);
+        doReturn(recentsContainer).when(mActivityInterface).getCreatedContainer();
+        doAnswer(answer -> {
+            answer.<Runnable>getArgument(0).run();
+            return this;
+        }).when(recentsContainer).runOnBindToTouchInteractionService(any());
+    }
+
+    @Test
+    public void testInitWhenReady_registersActivityInitListener() {
+        String reasonString = "because i said so";
+
+        createSwipeHandler().initWhenReady(reasonString);
+        verify(mActivityInitListener).register(eq(reasonString));
+    }
+
+    @Test
+    public void testOnRecentsAnimationCanceled_unregistersActivityInitListener() {
+        createSwipeHandler()
+                .onRecentsAnimationCanceled(new HashMap<>());
+
+        runOnMainSync(() -> verify(mActivityInitListener)
+                .unregister(eq("AbsSwipeUpHandler.onRecentsAnimationCanceled")));
+    }
+
+    @Test
+    public void testOnConsumerAboutToBeSwitched_unregistersActivityInitListener() {
+        createSwipeHandler().onConsumerAboutToBeSwitched();
+
+        runOnMainSync(() -> verify(mActivityInitListener)
+                .unregister("AbsSwipeUpHandler.invalidateHandler"));
+    }
+
+    @Test
+    public void testOnConsumerAboutToBeSwitched_midQuickSwitch_unregistersActivityInitListener() {
+        createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.NEW_TASK)
+                .onConsumerAboutToBeSwitched();
+
+        runOnMainSync(() -> verify(mActivityInitListener)
+                .unregister(eq("AbsSwipeUpHandler.cancelCurrentAnimation")));
+    }
+
+    @Test
+    public void testStartNewTask_finishesRecentsAnimationController() {
+        SWIPE_HANDLER absSwipeUpHandler = createSwipeHandler();
+
+        onRecentsAnimationStart(absSwipeUpHandler);
+
+        runOnMainSync(() -> {
+            absSwipeUpHandler.startNewTask(unused -> {});
+            verify(mRecentsAnimationController).finish(anyBoolean(), any());
+        });
+    }
+
+    @Test
+    public void testHomeGesture_finishesRecentsAnimationController() {
+        createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+
+        runOnMainSync(() -> {
+            verify(mRecentsAnimationController).detachNavigationBarFromApp(true);
+            verify(mRecentsAnimationController).finish(anyBoolean(), any(), anyBoolean());
+        });
+    }
+
+    private SWIPE_HANDLER createSwipeUpHandlerForGesture(GestureState.GestureEndTarget endTarget) {
+        boolean isQuickSwitch = endTarget == GestureState.GestureEndTarget.NEW_TASK;
+
+        doReturn(mState).when(mActivityInterface).stateFromGestureEndTarget(any());
+
+        SWIPE_HANDLER swipeHandler = createSwipeHandler(SystemClock.uptimeMillis(), isQuickSwitch);
+
+        swipeHandler.onActivityInit(/* alreadyOnHome= */ false);
+        swipeHandler.onGestureStarted(isQuickSwitch);
+        onRecentsAnimationStart(swipeHandler);
+
+        when(mGestureState.getRunningTaskIds(anyBoolean())).thenReturn(new int[0]);
+        runOnMainSync(swipeHandler::switchToScreenshot);
+
+        when(mGestureState.getEndTarget()).thenReturn(endTarget);
+        when(mGestureState.isRecentsAnimationRunning()).thenReturn(isQuickSwitch);
+        float xVelocityPxPerMs = isQuickSwitch ? 100 : 0;
+        float yVelocityPxPerMs = isQuickSwitch ? 0 : -100;
+        swipeHandler.onGestureEnded(
+                yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs));
+        swipeHandler.onCalculateEndTarget();
+        runOnMainSync(swipeHandler::onSettledOnEndTarget);
+
+        return swipeHandler;
+    }
+
+    private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) {
+        when(mActivityInterface.getOverviewWindowBounds(any(), any())).thenReturn(new Rect());
+        doNothing().when(mActivityInterface).setOnDeferredActivityLaunchCallback(any());
+
+        runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart(
+                mRecentsAnimationController, mRecentsAnimationTargets));
+    }
+
+    private static void runOnMainSync(Runnable runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+    }
+
+    @NonNull
+    private SWIPE_HANDLER createSwipeHandler() {
+        return createSwipeHandler(SystemClock.uptimeMillis(), false);
+    }
+
+    @NonNull
+    protected abstract SWIPE_HANDLER createSwipeHandler(
+            long touchTimeMs, boolean continuingLastGesture);
+
+    @NonNull
+    protected abstract RecentsViewContainer getRecentsContainer();
+
+    @NonNull
+    protected abstract RECENTS_VIEW getRecentsView();
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
new file mode 100644
index 0000000..dd0b4b3
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2024 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;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.util.LauncherMultivalentJUnit;
+import com.android.quickstep.fallback.FallbackRecentsView;
+import com.android.quickstep.fallback.RecentsState;
+
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit.class)
+public class FallbackSwipeHandlerTestCase extends AbsSwipeUpHandlerTestCase<
+        RecentsActivity,
+        RecentsState,
+        FallbackRecentsView,
+        RecentsActivity,
+        FallbackActivityInterface,
+        FallbackSwipeHandler> {
+
+    @Mock private RecentsActivity mRecentsActivity;
+    @Mock private FallbackRecentsView mRecentsView;
+
+
+    @Override
+    protected FallbackSwipeHandler createSwipeHandler(
+            long touchTimeMs, boolean continuingLastGesture) {
+        return new FallbackSwipeHandler(
+                mContext,
+                mRecentsAnimationDeviceState,
+                mTaskAnimationManager,
+                mGestureState,
+                touchTimeMs,
+                continuingLastGesture,
+                mInputConsumerController);
+    }
+
+    @Override
+    protected RecentsActivity getRecentsContainer() {
+        return mRecentsActivity;
+    }
+
+    @Override
+    protected FallbackRecentsView getRecentsView() {
+        return mRecentsView;
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
new file mode 100644
index 0000000..653dc01
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2024 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;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.WorkspaceStateTransitionAnimation;
+import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
+import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.LauncherMultivalentJUnit;
+import com.android.quickstep.views.RecentsView;
+
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+@SmallTest
+@RunWith(LauncherMultivalentJUnit.class)
+public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
+        QuickstepLauncher,
+        LauncherState,
+        RecentsView<QuickstepLauncher, LauncherState>,
+        QuickstepLauncher,
+        LauncherActivityInterface,
+        LauncherSwipeHandlerV2> {
+
+    @Mock private QuickstepLauncher mQuickstepLauncher;
+    @Mock private RecentsView<QuickstepLauncher, LauncherState> mRecentsView;
+    @Mock private Workspace<?> mWorkspace;
+    @Mock private Hotseat mHotseat;
+    @Mock private WorkspaceStateTransitionAnimation mTransitionAnimation;
+
+    @Before
+    public void setUpQuickStepLauncher() {
+        when(mQuickstepLauncher.createAtomicAnimationFactory())
+                .thenReturn(new AtomicAnimationFactory<>(0));
+        when(mQuickstepLauncher.getHotseat()).thenReturn(mHotseat);
+        doReturn(mWorkspace).when(mQuickstepLauncher).getWorkspace();
+        doReturn(new StateManager(mQuickstepLauncher, LauncherState.NORMAL))
+                .when(mQuickstepLauncher).getStateManager();
+
+    }
+
+    @Before
+    public void setUpWorkspace() {
+        when(mWorkspace.getStateTransitionAnimation()).thenReturn(mTransitionAnimation);
+    }
+
+    @Override
+    protected LauncherSwipeHandlerV2 createSwipeHandler(
+            long touchTimeMs, boolean continuingLastGesture) {
+        return new LauncherSwipeHandlerV2(
+                mContext,
+                mRecentsAnimationDeviceState,
+                mTaskAnimationManager,
+                mGestureState,
+                touchTimeMs,
+                continuingLastGesture,
+                mInputConsumerController);
+    }
+
+    @Override
+    protected QuickstepLauncher getRecentsContainer() {
+        return mQuickstepLauncher;
+    }
+
+    @Override
+    protected RecentsView<QuickstepLauncher, LauncherState> getRecentsView() {
+        return mRecentsView;
+    }
+}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5cbf6fb..8ae6d73 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -316,6 +316,74 @@
     // DragController
     public int flingToDeleteThresholdVelocity;
 
+    /** Used only as an alternative to mocking when null values cannot be used. */
+    @VisibleForTesting
+    public DeviceProfile() {
+        inv = null;
+        mInfo = null;
+        mMetrics = null;
+        mIconSizeSteps = null;
+        isTablet = false;
+        isPhone = false;
+        transposeLayoutWithOrientation = false;
+        isMultiDisplay = false;
+        isTwoPanels = false;
+        isPredictiveBackSwipe = false;
+        isQsbInline = false;
+        isLandscape = false;
+        isMultiWindowMode = false;
+        isGestureMode = false;
+        isLeftRightSplit = false;
+        windowX = 0;
+        windowY = 0;
+        widthPx = 0;
+        heightPx = 0;
+        availableWidthPx = 0;
+        availableHeightPx = 0;
+        rotationHint = 0;
+        aspectRatio = 1;
+        mIsScalableGrid = false;
+        mTypeIndex = 0;
+        mIsResponsiveGrid = false;
+        desiredWorkspaceHorizontalMarginOriginalPx = 0;
+        edgeMarginPx = 0;
+        workspaceContentScale = 0;
+        workspaceSpringLoadedMinNextPageVisiblePx = 0;
+        extraSpace = 0;
+        workspacePageIndicatorHeight = 0;
+        mWorkspacePageIndicatorOverlapWorkspace = 0;
+        numFolderRows = 0;
+        numFolderColumns = 0;
+        folderLabelTextScale = 0;
+        areNavButtonsInline = false;
+        mHotseatBarEdgePaddingPx = 0;
+        mHotseatBarWorkspaceSpacePx = 0;
+        hotseatQsbWidth = 0;
+        hotseatQsbHeight = 0;
+        hotseatQsbVisualHeight = 0;
+        hotseatQsbShadowHeight = 0;
+        hotseatBorderSpace = 0;
+        mMinHotseatIconSpacePx = 0;
+        mMinHotseatQsbWidthPx = 0;
+        mMaxHotseatIconSpacePx = 0;
+        inlineNavButtonsEndSpacingPx = 0;
+        mBubbleBarSpaceThresholdPx = 0;
+        numShownAllAppsColumns = 0;
+        overviewActionsHeight = 0;
+        overviewActionsTopMarginPx = 0;
+        overviewActionsButtonSpacing = 0;
+        mViewScaleProvider = null;
+        mDotRendererWorkSpace = null;
+        mDotRendererAllApps = null;
+        taskbarHeight = 0;
+        stashedTaskbarHeight = 0;
+        taskbarBottomMargin = 0;
+        taskbarIconSize = 0;
+        mTransientTaskbarClaimedSpace = 0;
+        startAlignTaskbar = false;
+        isTransientTaskbar = false;
+    }
+
     /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
     DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
             SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,