/*
 * Copyright (C) 2016 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.server.wm;

import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_OPEN;

import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.POSITION_TOP;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;

import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.ArraySet;
import android.view.Display;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;

import androidx.test.filters.SmallTest;

import com.android.internal.policy.TransitionAnimation;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Test class for {@link AppTransition}.
 *
 * Build/Install/Run:
 *  atest WmTests:AppTransitionTests
 */
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class AppTransitionTests extends WindowTestsBase {
    private DisplayContent mDc;

    @Before
    public void setUp() throws Exception {
        doNothing().when(mWm.mRoot).performSurfacePlacement();
        mDc = mWm.getDefaultDisplayContentLocked();
    }

    @Test
    public void testKeyguardOverride() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
        mDc.mOpeningApps.add(activity);
        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testKeyguardUnoccludeOcclude() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE);
        mDc.prepareAppTransition(TRANSIT_KEYGUARD_OCCLUDE);
        mDc.mOpeningApps.add(activity);
        assertEquals(TRANSIT_NONE,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));

    }

    @Test
    public void testKeyguardKeep() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.mOpeningApps.add(activity);
        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testCrashing() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
        mDc.mClosingApps.add(activity);
        assertEquals(TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testKeepKeyguard_withCrashing() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY);
        mDc.prepareAppTransition(TRANSIT_CLOSE, TRANSIT_FLAG_APP_CRASHED);
        mDc.mClosingApps.add(activity);
        assertEquals(TRANSIT_OLD_KEYGUARD_GOING_AWAY,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testSkipTransitionAnimation() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        final ActivityRecord activity = createActivityRecord(dc);

        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.prepareAppTransition(TRANSIT_CLOSE);
        mDc.mClosingApps.add(activity);
        assertEquals(TRANSIT_OLD_UNSET,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, true /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testTaskChangeWindowingMode() {
        final ActivityRecord activity = createActivityRecord(mDc);

        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.prepareAppTransition(TRANSIT_CHANGE);
        mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
        mDc.mChangingContainers.add(activity.getTask());

        assertEquals(TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testTaskFragmentChange() {
        final ActivityRecord activity = createActivityRecord(mDc);
        final TaskFragment taskFragment = new TaskFragment(mAtm, new Binder(),
                true /* createdByOrganizer */, true /* isEmbedded */);
        activity.getTask().addChild(taskFragment, POSITION_TOP);
        activity.reparent(taskFragment, POSITION_TOP);

        mDc.prepareAppTransition(TRANSIT_OPEN);
        mDc.prepareAppTransition(TRANSIT_CHANGE);
        mDc.mOpeningApps.add(activity); // Make sure TRANSIT_CHANGE has the priority
        mDc.mChangingContainers.add(taskFragment);

        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
                AppTransitionController.getTransitCompatType(mDc.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /*skipAppTransitionAnimation*/));
    }

    @Test
    public void testTaskFragmentOpeningTransition() {
        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
        activity.setVisible(false);

        mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
        mDisplayContent.mOpeningApps.add(activity);
        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_OPEN,
                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
    }

    @Test
    public void testTaskFragmentClosingTransition() {
        final ActivityRecord activity = createHierarchyForTaskFragmentTest();
        activity.setVisible(true);

        mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
        mDisplayContent.mClosingApps.add(activity);
        assertEquals(TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
                AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
                        mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
                        mDisplayContent.mChangingContainers, null /* wallpaperTarget */,
                        null /* oldWallpaper */, false /* skipAppTransitionAnimation */));
    }

    /**
     * Creates a {@link Task} with two {@link TaskFragment TaskFragments}.
     * The bottom TaskFragment is to prevent
     * {@link AppTransitionController#getAnimationTargets(ArraySet, ArraySet, boolean) the animation
     * target} to promote to Task or above.
     *
     * @return The Activity to be put in either opening or closing Activity
     */
    private ActivityRecord createHierarchyForTaskFragmentTest() {
        final Task parentTask = createTask(mDisplayContent);
        final TaskFragment bottomTaskFragment = createTaskFragmentWithActivity(parentTask);
        final ActivityRecord bottomActivity = bottomTaskFragment.getTopMostActivity();
        bottomActivity.setOccludesParent(true);
        bottomActivity.setVisible(true);

        final TaskFragment verifiedTaskFragment = createTaskFragmentWithActivity(parentTask);
        final ActivityRecord activity = verifiedTaskFragment.getTopMostActivity();
        activity.setOccludesParent(true);

        return activity;
    }

    @Test
    public void testAppTransitionStateForMultiDisplay() {
        // Create 2 displays & presume both display the state is ON for ready to display & animate.
        final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
        final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);

        // Create 2 app window tokens to represent 2 activity window.
        final ActivityRecord activity1 = createActivityRecord(dc1);
        final ActivityRecord activity2 = createActivityRecord(dc2);

        activity1.allDrawn = true;
        activity1.startingMoved = true;

        // Simulate activity resume / finish flows to prepare app transition & set visibility,
        // make sure transition is set as expected for each display.
        dc1.prepareAppTransition(TRANSIT_OPEN);
        dc2.prepareAppTransition(TRANSIT_CLOSE);
        // One activity window is visible for resuming & the other activity window is invisible
        // for finishing in different display.
        activity1.setVisibility(true);
        activity2.setVisibility(false);

        // Make sure each display is in animating stage.
        assertTrue(dc1.mOpeningApps.size() > 0);
        assertTrue(dc2.mClosingApps.size() > 0);
        assertTrue(dc1.isAppTransitioning());
        assertTrue(dc2.isAppTransitioning());
    }

    @Test
    public void testCleanAppTransitionWhenRootTaskReparent() {
        // Create 2 displays & presume both display the state is ON for ready to display & animate.
        final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
        final DisplayContent dc2 = createNewDisplay(Display.STATE_ON);

        final Task rootTask1 = createTask(dc1);
        final Task task1 = createTaskInRootTask(rootTask1, 0 /* userId */);
        final ActivityRecord activity1 = createNonAttachedActivityRecord(dc1);
        task1.addChild(activity1, 0);

        // Simulate same app is during opening / closing transition set stage.
        dc1.mClosingApps.add(activity1);
        assertTrue(dc1.mClosingApps.size() > 0);

        dc1.prepareAppTransition(TRANSIT_OPEN);
        assertTrue(dc1.mAppTransition.containsTransitRequest(TRANSIT_OPEN));
        assertTrue(dc1.mAppTransition.isTransitionSet());

        dc1.mOpeningApps.add(activity1);
        assertTrue(dc1.mOpeningApps.size() > 0);

        // Move root task to another display.
        rootTask1.reparent(dc2.getDefaultTaskDisplayArea(), true);

        // Verify if token are cleared from both pending transition list in former display.
        assertFalse(dc1.mOpeningApps.contains(activity1));
        assertFalse(dc1.mOpeningApps.contains(activity1));
    }

    @Test
    public void testLoadAnimationSafely() {
        DisplayContent dc = createNewDisplay(Display.STATE_ON);
        assertNull(dc.mAppTransition.loadAnimationSafely(
                getInstrumentation().getTargetContext(), -1));
    }

    @Test
    public void testCancelRemoteAnimationWhenFreeze() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        doReturn(false).when(dc).onDescendantOrientationChanged(any());
        final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
                dc, "exiting app");
        final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
        // Wait until everything in animation handler get executed to prevent the exiting window
        // from being removed during WindowSurfacePlacer Traversal.
        waitUntilHandlersIdle();

        // Set a remote animator.
        final TestRemoteAnimationRunner runner = new TestRemoteAnimationRunner();
        final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
                runner, 100, 50, true /* changeNeedsSnapshot */);
        // RemoteAnimationController will tracking RemoteAnimationAdapter's caller with calling pid.
        adapter.setCallingPidUid(123, 456);

        // Simulate activity finish flows to prepare app transition & set visibility,
        // make sure transition is set as expected.
        dc.prepareAppTransition(TRANSIT_CLOSE);
        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_CLOSE));
        dc.mAppTransition.overridePendingAppTransitionRemote(adapter);
        exitingActivity.setVisibility(false);
        assertTrue(dc.mClosingApps.size() > 0);

        // Make sure window is in animating stage before freeze, and cancel after freeze.
        assertTrue(dc.isAppTransitioning());
        assertFalse(runner.mCancelled);
        dc.mAppTransition.freeze();
        assertFalse(dc.isAppTransitioning());
        assertTrue(runner.mCancelled);
    }

    @Test
    public void testDelayWhileRecents() {
        final DisplayContent dc = createNewDisplay(Display.STATE_ON);
        doReturn(false).when(dc).onDescendantOrientationChanged(any());
        final Task task = createTask(dc);

        // Simulate activity1 launches activity2.
        final ActivityRecord activity1 = createActivityRecord(task);
        activity1.setVisible(true);
        activity1.setVisibleRequested(false);
        activity1.allDrawn = true;
        final ActivityRecord activity2 = createActivityRecord(task);
        activity2.setVisible(false);
        activity2.setVisibleRequested(true);
        activity2.allDrawn = true;

        dc.mClosingApps.add(activity1);
        dc.mOpeningApps.add(activity2);
        dc.prepareAppTransition(TRANSIT_OPEN);
        assertTrue(dc.mAppTransition.containsTransitRequest(TRANSIT_OPEN));

        // Wait until everything in animation handler get executed to prevent the exiting window
        // from being removed during WindowSurfacePlacer Traversal.
        waitUntilHandlersIdle();

        // Start recents
        doReturn(true).when(task)
                .isSelfAnimating(anyInt(), eq(ANIMATION_TYPE_RECENTS));

        dc.mAppTransitionController.handleAppTransitionReady();

        verify(activity1, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
        verify(activity2, never()).commitVisibility(anyBoolean(), anyBoolean(), anyBoolean());
    }

    @Test
    public void testGetAnimationStyleResId() {
        // Verify getAnimationStyleResId will return as LayoutParams.windowAnimations when without
        // specifying window type.
        final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
        attrs.windowAnimations = 0x12345678;
        assertEquals(attrs.windowAnimations, mDc.mAppTransition.getAnimationStyleResId(attrs));

        // Verify getAnimationStyleResId will return system resource Id when the window type is
        // starting window.
        attrs.type = TYPE_APPLICATION_STARTING;
        assertEquals(mDc.mAppTransition.getDefaultWindowAnimationStyleResId(),
                mDc.mAppTransition.getAnimationStyleResId(attrs));
    }

    @Test
    public void testActivityRecordReparentedToTaskFragment() {
        final ActivityRecord activity = createActivityRecord(mDc);
        final SurfaceControl activityLeash = mock(SurfaceControl.class);
        doNothing().when(activity).setDropInputMode(anyInt());
        activity.setVisibility(true);
        activity.setSurfaceControl(activityLeash);
        final Task task = activity.getTask();

        // Add a TaskFragment of half of the Task size.
        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
        final ITaskFragmentOrganizer iOrganizer =
                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
        registerTaskFragmentOrganizer(iOrganizer);
        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                .setParentTask(task)
                .setOrganizer(organizer)
                .build();
        final Rect taskBounds = new Rect();
        task.getBounds(taskBounds);
        taskFragment.setBounds(0, 0, taskBounds.right / 2, taskBounds.bottom);
        spyOn(taskFragment);
        mockSurfaceFreezerSnapshot(taskFragment.mSurfaceFreezer);

        assertTrue(mDc.mChangingContainers.isEmpty());
        assertFalse(mDc.mAppTransition.isTransitionSet());

        // Schedule app transition when reparent activity to a TaskFragment of different size.
        final Rect startBounds = new Rect(activity.getBounds());
        activity.reparent(taskFragment, POSITION_TOP);

        // It should transit at TaskFragment level with snapshot on the activity surface.
        verify(taskFragment).initializeChangeTransition(activity.getBounds(), activityLeash);
        assertTrue(mDc.mChangingContainers.contains(taskFragment));
        assertTrue(mDc.mAppTransition.containsTransitRequest(TRANSIT_CHANGE));
        assertEquals(startBounds, taskFragment.mSurfaceFreezer.mFreezeBounds);
    }

    @Test
    public void testGetNextAppTransitionBackgroundColor() {
        assumeFalse(WindowManagerService.sEnableShellTransitions);

        // No override by default.
        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());

        // Override with a custom color.
        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
        final int testColor = 123;
        mDc.mAppTransition.overridePendingAppTransition("testPackage", 0 /* enterAnim */,
                0 /* exitAnim */, testColor, null /* startedCallback */, null /* endedCallback */,
                false /* overrideTaskTransaction */);

        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());

        // Override with ActivityEmbedding remote animation. Background color should be kept.
        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
                false /* sync */, true /* isActivityEmbedding */);

        assertEquals(testColor, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());

        // Background color should not be cleared anymore after #clear().
        mDc.mAppTransition.clear();
        assertEquals(0, mDc.mAppTransition.getNextAppTransitionBackgroundColor());
        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());
    }

    @Test
    public void testGetNextAppRequestedAnimation() {
        assumeFalse(WindowManagerService.sEnableShellTransitions);
        final String packageName = "testPackage";
        final int enterAnimResId = 1;
        final int exitAnimResId = 2;
        final int testColor = 123;
        final Animation enterAnim = mock(Animation.class);
        final Animation exitAnim = mock(Animation.class);
        final TransitionAnimation transitionAnimation = mDc.mAppTransition.mTransitionAnimation;
        spyOn(transitionAnimation);
        doReturn(enterAnim).when(transitionAnimation)
                .loadAppTransitionAnimation(packageName, enterAnimResId);
        doReturn(exitAnim).when(transitionAnimation)
                .loadAppTransitionAnimation(packageName, exitAnimResId);

        // No override by default.
        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));

        // Override with a custom animation.
        mDc.mAppTransition.prepareAppTransition(TRANSIT_OPEN, 0);
        mDc.mAppTransition.overridePendingAppTransition(packageName, enterAnimResId, exitAnimResId,
                testColor, null /* startedCallback */, null /* endedCallback */,
                false /* overrideTaskTransaction */);

        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
        assertTrue(mDc.mAppTransition.isNextAppTransitionOverrideRequested());

        // Override with ActivityEmbedding remote animation. Custom animation should be kept.
        mDc.mAppTransition.overridePendingAppTransitionRemote(mock(RemoteAnimationAdapter.class),
                false /* sync */, true /* isActivityEmbedding */);

        assertEquals(enterAnim, mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
        assertEquals(exitAnim, mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
        assertFalse(mDc.mAppTransition.isNextAppTransitionOverrideRequested());

        // Custom animation should not be cleared anymore after #clear().
        mDc.mAppTransition.clear();
        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(true /* enter */));
        assertNull(mDc.mAppTransition.getNextAppRequestedAnimation(false /* enter */));
    }

    private class TestRemoteAnimationRunner implements IRemoteAnimationRunner {
        boolean mCancelled = false;
        @Override
        public void onAnimationStart(@WindowManager.TransitionOldType int transit,
                RemoteAnimationTarget[] apps,
                RemoteAnimationTarget[] wallpapers,
                RemoteAnimationTarget[] nonApps,
                IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
        }

        @Override
        public void onAnimationCancelled() {
            mCancelled = true;
        }

        @Override
        public IBinder asBinder() {
            return null;
        }
    }
}
