Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a4ce092b authored by Schneider Victor-tulias's avatar Schneider Victor-tulias Committed by Android (Google) Code Review
Browse files

Merge "Add a test base for AbsSwipeUpHandler and its subclasses" into main

parents a2dc65a2 f5035847
Loading
Loading
Loading
Loading
+20 −15
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ import android.window.PictureInPictureSurfaceTransaction;
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 @@ import java.util.function.Consumer;
/**
 * 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 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
    // 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 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
    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 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
            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 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
    }

    private void onLauncherStart() {
        final T container = (T) mContainerInterface.getCreatedContainer();
        final RECENTS_CONTAINER container = mContainerInterface.getCreatedContainer();
        if (container == null || mContainer != container) {
            return;
        }
@@ -1096,7 +1099,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
     */
    @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
@@ -1167,7 +1170,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
    /**
     * 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) {
@@ -1180,7 +1184,8 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
        }
    }

    private void onSettledOnEndTarget() {
    @VisibleForTesting
    protected void onSettledOnEndTarget() {
        // Fast-finish the attaching animation if it's still running.
        maybeUpdateRecentsAttachedState(false);
        final GestureEndTarget endTarget = mGestureState.getEndTarget();
@@ -1416,7 +1421,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
            }
        }
        Interpolator interpolator;
        S state = mContainerInterface.stateFromGestureEndTarget(endTarget);
        STATE state = mContainerInterface.stateFromGestureEndTarget(endTarget);
        if (isKeyboardTaskFocusPending()) {
            interpolator = EMPHASIZED;
        } else if (state.displayOverviewTasksAsGrid(mDp)) {
@@ -2497,7 +2502,7 @@ public abstract class AbsSwipeUpHandler<T extends RecentsViewContainer,
    }

    private void animateSplashScreenExit(
            @NonNull T activity,
            @NonNull RECENTS_CONTAINER activity,
            @NonNull RemoteAnimationTarget[] appearedTaskTargets,
            @NonNull RemoteAnimationTarget[] animatingTargets) {
        ViewGroup splashView = activity.getDragLayer();
+2 −2
Original line number Diff line number Diff line
@@ -62,8 +62,8 @@ import java.util.List;
/**
 * 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,
+1 −1
Original line number Diff line number Diff line
@@ -338,7 +338,7 @@ class OverviewCommandHelper(
            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)
+276 −0
Original line number Diff line number Diff line
/*
 * 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();
}
+64 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
Loading