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

Commit b6557fa1 authored by wilsonshih's avatar wilsonshih
Browse files

Add some unit tests for PB transitions

Flag: com.android.window.flags.migrate_predictive_back_transition
Bug: 358422448
Test: atest BackAnimationControllerTest
Change-Id: I7f3331c6887370279d6be0c2edf9c2500e04a274
parent ef6bba06
Loading
Loading
Loading
Loading
+8 −4
Original line number Diff line number Diff line
@@ -146,7 +146,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    private final Handler mBgHandler;
    private final WindowManager mWindowManager;
    private final Transitions mTransitions;
    private final BackTransitionHandler mBackTransitionHandler;
    @VisibleForTesting
    final BackTransitionHandler mBackTransitionHandler;
    @VisibleForTesting
    final Rect mTouchableArea = new Rect();

@@ -174,7 +175,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    @Nullable
    private IOnBackInvokedCallback mActiveCallback;
    @Nullable
    private RemoteAnimationTarget[] mApps;
    @VisibleForTesting
    RemoteAnimationTarget[] mApps;

    @VisibleForTesting
    final RemoteCallback mNavigationObserver = new RemoteCallback(
@@ -1448,7 +1450,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
         * Check whether this transition is prepare for predictive back animation, which could
         * happen when core make an activity become visible.
         */
        private boolean handlePrepareTransition(
        @VisibleForTesting
        boolean handlePrepareTransition(
                @NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction st,
                @NonNull SurfaceControl.Transaction ft,
@@ -1491,7 +1494,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
         * Check whether this transition is triggered from back gesture commitment.
         * Reparent the transition targets to animation leashes, so the animation won't be broken.
         */
        private boolean handleCloseTransition(@NonNull TransitionInfo info,
        @VisibleForTesting
        boolean handleCloseTransition(@NonNull TransitionInfo info,
                @NonNull SurfaceControl.Transaction st,
                @NonNull SurfaceControl.Transaction ft,
                @NonNull Transitions.TransitionFinishCallback finishCallback) {
+215 −0
Original line number Diff line number Diff line
@@ -16,8 +16,19 @@

package com.android.wm.shell.back;

import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION;
import static android.window.BackNavigationInfo.KEY_NAVIGATION_FINISHED;
import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +36,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -32,6 +44,7 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import android.app.ActivityManager;
import android.app.IActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
@@ -40,6 +53,7 @@ import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.provider.Settings;
@@ -51,11 +65,16 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.BackEvent;
import android.window.BackMotionEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
import android.window.IWindowContainerToken;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;

import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -128,6 +147,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
    private ShellBackAnimationRegistry mShellBackAnimationRegistry;
    private Rect mTouchableRegion;

    private BackAnimationController.BackTransitionHandler mBackTransitionHandler;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
@@ -165,6 +186,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
        mShellExecutor.flushAll();
        mTouchableRegion = new Rect(0, 0, 100, 100);
        mController.mTouchableArea.set(mTouchableRegion);
        mBackTransitionHandler = mController.mBackTransitionHandler;
        spyOn(mBackTransitionHandler);
    }

    private void createNavigationInfo(int backType,
@@ -606,6 +629,198 @@ public class BackAnimationControllerTest extends ShellTestCase {
                mCrossTaskBackAnimation.getRunner());
    }

    @Test
    public void testCloseAsExpectTransition() {
        final int openTaskId = 1;
        final int closeTaskId = 2;
        mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
        final IBinder mockBinder = mock(IBinder.class);
        final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
        final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
        // Single close
        final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
                FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
        final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
                FLAG_BACK_GESTURE_ANIMATED);

        TransitionInfo tInfo = createTransitionInfo(TRANSIT_CLOSE, open, close);
        mBackTransitionHandler.mCloseTransitionRequested = true;
        Transitions.TransitionFinishCallback callback =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
        verify(mBackTransitionHandler).handleCloseTransition(
                eq(tInfo), eq(st), eq(ft), eq(callback));
        mBackTransitionHandler.onAnimationFinished();
        verify(callback).onTransitionFinished(any());
        mBackTransitionHandler.mCloseTransitionRequested = false;

        // PREPARE + CLOSE
        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
        callback = mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
        verify(mBackTransitionHandler).handlePrepareTransition(
                eq(tInfo), eq(st), eq(ft), eq(callback));
        mBackTransitionHandler.mCloseTransitionRequested = true;
        TransitionInfo tInfo2 = createTransitionInfo(TRANSIT_CLOSE, close);
        Transitions.TransitionFinishCallback mergeCallback =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.mergeAnimation(
                mock(IBinder.class), tInfo2, st, mock(IBinder.class), mergeCallback);
        mBackTransitionHandler.onAnimationFinished();
        verify(callback).onTransitionFinished(any());
        verify(mergeCallback).onTransitionFinished(any());
        mBackTransitionHandler.mCloseTransitionRequested = false;

        // PREPARE contains close info
        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open, close);
        callback = mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.mCloseTransitionRequested = true;
        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
        verify(mBackTransitionHandler).handleCloseTransition(
                eq(tInfo), eq(st), eq(ft), eq(callback));
        mBackTransitionHandler.onAnimationFinished();
        verify(callback).onTransitionFinished(any());
        mBackTransitionHandler.mCloseTransitionRequested = false;

        // PREPARE then Cancel
        tInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
        callback = mock(Transitions.TransitionFinishCallback.class);
        final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
                TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
                null /* remoteTransition */);
        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
        mBackTransitionHandler.startAnimation(mockBinder, tInfo, st, ft, callback);
        verify(mBackTransitionHandler).handlePrepareTransition(
                eq(tInfo), eq(st), eq(ft), eq(callback));
        final TransitionInfo.Change openToClose = createAppChange(openTaskId, TRANSIT_CLOSE,
                FLAG_BACK_GESTURE_ANIMATED);
        tInfo2 = createTransitionInfo(TRANSIT_CLOSE_PREPARE_BACK_NAVIGATION, openToClose);
        mBackTransitionHandler.mClosePrepareTransition = mock(IBinder.class);
        mergeCallback = mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.mergeAnimation(mBackTransitionHandler.mClosePrepareTransition,
                tInfo2, st, mock(IBinder.class), mergeCallback);
        assertTrue("Change should be consumed", tInfo2.getChanges().isEmpty());
        mBackTransitionHandler.onAnimationFinished();
        verify(callback).onTransitionFinished(any());
    }

    @Test
    public void testCancelUnexpectedTransition() {
        final int openTaskId = 1;
        final int closeTaskId = 2;
        mController.mApps = createAppAnimationTargets(openTaskId, closeTaskId);
        final IBinder mockBinder = mock(IBinder.class);
        final SurfaceControl.Transaction st = mock(SurfaceControl.Transaction.class);
        final SurfaceControl.Transaction ft = mock(SurfaceControl.Transaction.class);
        final TransitionInfo.Change open = createAppChange(openTaskId, TRANSIT_OPEN,
                FLAG_BACK_GESTURE_ANIMATED | FLAG_MOVED_TO_TOP);
        final TransitionInfo.Change close = createAppChange(closeTaskId, TRANSIT_CLOSE,
                FLAG_BACK_GESTURE_ANIMATED);

        // Didn't trigger close transition
        mBackTransitionHandler.mCloseTransitionRequested = false;
        TransitionInfo prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION,
                open, close);
        final Transitions.TransitionFinishCallback callback =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.handleRequest(mockBinder, mock(TransitionRequestInfo.class));
        boolean canHandle = mBackTransitionHandler.startAnimation(
                mockBinder, prepareInfo, st, ft, callback);
        assertFalse("Should not handle transition", canHandle);
        assertNull(mBackTransitionHandler.mOnAnimationFinishCallback);

        // Didn't trigger close transition, but receive close target.
        final TransitionRequestInfo requestInfo = new TransitionRequestInfo(
                TRANSIT_PREPARE_BACK_NAVIGATION, null /* triggerTask */,
                null /* remoteTransition */);
        prepareInfo = createTransitionInfo(TRANSIT_PREPARE_BACK_NAVIGATION, open);
        final Transitions.TransitionFinishCallback callback2 =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
        canHandle = mBackTransitionHandler.startAnimation(mockBinder,
                prepareInfo, st, ft, callback2);
        assertTrue("Handle prepare transition" , canHandle);
        verify(mBackTransitionHandler).handlePrepareTransition(
                eq(prepareInfo), eq(st), eq(ft), eq(callback2));
        final TransitionInfo closeInfo = createTransitionInfo(TRANSIT_CLOSE, close);
        Transitions.TransitionFinishCallback mergeCallback =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), closeInfo, ft,
                mock(IBinder.class), mergeCallback);
        verify(callback2).onTransitionFinished(any());
        verify(mergeCallback, never()).onTransitionFinished(any());

        // Didn't trigger close transition, but contains open target.
        final int openTaskId2 = 3;
        final Transitions.TransitionFinishCallback callback3 =
                mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.handleRequest(mockBinder, requestInfo);
        canHandle = mBackTransitionHandler.startAnimation(
                mockBinder, prepareInfo, st, ft, callback3);
        assertTrue("Handle prepare transition" , canHandle);
        verify(mBackTransitionHandler).handlePrepareTransition(
                eq(prepareInfo), eq(st), eq(ft), eq(callback3));
        final TransitionInfo.Change open2 = createAppChange(
                openTaskId2, TRANSIT_OPEN, FLAG_MOVED_TO_TOP);
        final TransitionInfo openInfo = createTransitionInfo(TRANSIT_OPEN, open2, close);
        mergeCallback = mock(Transitions.TransitionFinishCallback.class);
        mBackTransitionHandler.mergeAnimation(mock(IBinder.class), openInfo, ft,
                mock(IBinder.class), mergeCallback);
        verify(callback3).onTransitionFinished(any());
        verify(mergeCallback, never()).onTransitionFinished(any());
    }

    private RemoteAnimationTarget[] createAppAnimationTargets(int openTaskId, int closeTaskId) {
        final RemoteAnimationTarget openT = createSingleAnimationTarget(openTaskId,
                RemoteAnimationTarget.MODE_OPENING);
        final RemoteAnimationTarget closeT = createSingleAnimationTarget(closeTaskId,
                RemoteAnimationTarget.MODE_CLOSING);
        return new RemoteAnimationTarget[]{openT, closeT};
    }

    private RemoteAnimationTarget createSingleAnimationTarget(int taskId, int mode) {
        final Rect fakeR = new Rect();
        final Point fakeP = new Point();
        final ActivityManager.RunningTaskInfo openTaskInfo = new ActivityManager.RunningTaskInfo();
        openTaskInfo.taskId = taskId;
        openTaskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
        return new RemoteAnimationTarget(
                taskId, mode, mock(SurfaceControl.class), false, fakeR, fakeR,
                0, fakeP, fakeR, fakeR, new WindowConfiguration(), false,
                mock(SurfaceControl.class), fakeR, openTaskInfo, false);
    }
    private TransitionInfo.Change createAppChange(
            int taskId, @TransitionInfo.TransitionMode int mode,
            @TransitionInfo.ChangeFlags int flags) {
        final TransitionInfo.Change change;
        SurfaceControl.Builder b = new SurfaceControl.Builder()
                .setName("test task");
        if (taskId != INVALID_TASK_ID) {
            final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
            taskInfo.taskId = taskId;
            taskInfo.token = new WindowContainerToken(mock(IWindowContainerToken.class));
            change = new TransitionInfo.Change(
                    taskInfo.token, b.build());
            change.setTaskInfo(taskInfo);
        } else {
            change = new TransitionInfo.Change(
                null, b.build());

        }
        change.setMode(mode);
        change.setFlags(flags);
        return change;
    }

    private static TransitionInfo createTransitionInfo(
            @WindowManager.TransitionType int type, TransitionInfo.Change ... changes) {
        final TransitionInfo info = new TransitionInfo(type, 0);
        for (int i = 0; i < changes.length; ++i) {
            info.addChange(changes[i]);
        }
        return info;
    }

    private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
            throws RemoteException {
        final BackAnimationRunner animationRunner = spy(animation);