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

Commit afb770c7 authored by Vinit Nayak's avatar Vinit Nayak Committed by Android (Google) Code Review
Browse files

Merge "Allow single root candidate for app pair launch for pip edge case" into 24D1-dev

parents 58a01874 55270a9f
Loading
Loading
Loading
Loading
+213 −85
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.content.Context
import android.graphics.Bitmap
@@ -51,6 +52,7 @@ import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.apppairs.AppPairIcon
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.logging.StatsLogManager.EventEnum
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.statehandlers.DepthController
import com.android.launcher3.statemanager.StateManager
import com.android.launcher3.statemanager.StatefulActivity
@@ -69,6 +71,7 @@ import com.android.quickstep.views.TaskThumbnailView
import com.android.quickstep.views.TaskView
import com.android.quickstep.views.TaskView.TaskIdAttributeContainer
import com.android.quickstep.views.TaskViewIcon
import com.android.wm.shell.shared.TransitionUtil
import java.util.Optional
import java.util.function.Supplier

@@ -532,8 +535,14 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
            check(info != null && t != null) {
                "trying to launch an app pair icon, but encountered an unexpected null"
            }

            val appPairLaunchingAppIndex = hasChangesForBothAppPairs(launchingIconView, info)
            if (appPairLaunchingAppIndex == -1) {
                // Launch split app pair animation
                composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
            } else {
                composeFullscreenIconSplitLaunchAnimator(launchingIconView, info, t,
                        finishCallback, appPairLaunchingAppIndex)
            }
        } else {
            // Fallback case: simple fade-in animation
            check(info != null && t != null) {
@@ -597,6 +606,39 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
        )
    }

    /**
     * @return -1 if [transitionInfo] contains both apps of the app pair to be animated, otherwise
     *         the integer index corresponding to [launchingIconView]'s contents for the single app
     *         to be animated
     */
    fun hasChangesForBothAppPairs(launchingIconView: AppPairIcon,
                                          transitionInfo: TransitionInfo) : Int {
        val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
        val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
        var launchFullscreenAppIndex = -1
        for (change in transitionInfo.changes) {
            val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
            if (TransitionUtil.isOpeningType(change.mode) &&
                    taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN) {
                val baseIntent = taskInfo.baseIntent.component?.packageName
                if (baseIntent == intent1) {
                    if (launchFullscreenAppIndex > -1) {
                        launchFullscreenAppIndex = -1
                        break
                    }
                    launchFullscreenAppIndex = 0
                } else if (baseIntent == intent2) {
                    if (launchFullscreenAppIndex > -1) {
                        launchFullscreenAppIndex = -1
                        break
                    }
                    launchFullscreenAppIndex = 1
                }
            }
        }
        return launchFullscreenAppIndex
    }

    /**
     * When the user taps an app pair icon to launch split, this will play the tasks' launch
     * animation from the position of the icon.
@@ -632,7 +674,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
        // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
        // use the scale-up animation
        if (launchingIconView.context is TaskbarActivityContext) {
            composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback)
            composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
                    WINDOWING_MODE_MULTI_WINDOW)
            return
        }

@@ -642,11 +685,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC

        // Create an AnimatorSet that will run both shell and launcher transitions together
        val launchAnimation = AnimatorSet()
        val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
        val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
        progressUpdater.setDuration(timings.getDuration().toLong())
        progressUpdater.interpolator = Interpolators.LINEAR

        var rootCandidate: Change? = null

        for (change in transitionInfo.changes) {
@@ -690,27 +728,13 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
        // Make sure nothing weird happened, like getChange() returning null.
        check(rootCandidate != null) { "Failed to find a root leash" }

        // Shell animation: the apps are revealed toward end of the launch animation
        progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
            val progress =
                Interpolators.clampToProgress(
                    Interpolators.LINEAR,
                    valueAnimator.animatedFraction,
                    timings.appRevealStartOffset,
                    timings.appRevealEndOffset
                )

            // Set the alpha of the shell layer (2 apps + divider)
            t.setAlpha(rootCandidate.leash, progress)
            t.apply()
        }

        // Create a new floating view in Launcher, positioned above the launching icon
        val drawableArea = launchingIconView.iconDrawableArea
        val appIcon1 = launchingIconView.info.getFirstApp().newIcon(launchingIconView.context)
        val appIcon2 = launchingIconView.info.getSecondApp().newIcon(launchingIconView.context)
        appIcon1.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)
        appIcon2.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)

        val floatingView =
            FloatingAppPairView.getFloatingAppPairView(
                launcher,
@@ -721,7 +745,112 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
            )
        floatingView.bringToFront()

        // Launcher animation: animate the floating view, expanding to fill the display surface
        launchAnimation.play(
                getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
                        rootCandidate))
        launchAnimation.start()
    }

    /**
     * Similar to [composeIconSplitLaunchAnimator], but instructs [FloatingAppPairView] to animate
     * a single fullscreen icon + background instead of for a pair
     */
    @VisibleForTesting
    fun composeFullscreenIconSplitLaunchAnimator(
            launchingIconView: AppPairIcon,
            transitionInfo: TransitionInfo,
            t: Transaction,
            finishCallback: Runnable,
            launchFullscreenIndex: Int
    ) {
        // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
        // use the scale-up animation
        if (launchingIconView.context is TaskbarActivityContext) {
            composeScaleUpLaunchAnimation(transitionInfo, t, finishCallback,
                    WINDOWING_MODE_FULLSCREEN)
            return
        }

        // Else we are in Launcher and can launch with the full icon stretch-and-split animation.
        val launcher = Launcher.getLauncher(launchingIconView.context)
        val dp = launcher.deviceProfile

        // Create an AnimatorSet that will run both shell and launcher transitions together
        val launchAnimation = AnimatorSet()

        val appInfo = launchingIconView.info
                .getContents()[launchFullscreenIndex] as WorkspaceItemInfo
        val intentToLaunch = appInfo.intent.component?.packageName
        var rootCandidate: Change? = null
        for (change in transitionInfo.changes) {
            val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
            val baseIntent = taskInfo.baseIntent.component?.packageName
            if (TransitionUtil.isOpeningType(change.mode) &&
                    taskInfo.windowingMode == WINDOWING_MODE_FULLSCREEN &&
                    baseIntent == intentToLaunch) {
                rootCandidate = change
            }
        }

        // If we could not find a proper root candidate, something went wrong.
        check(rootCandidate != null) { "Could not find a split root candidate" }

        // Recurse up the tree until parent is null, then we've found our root.
        var parentToken: WindowContainerToken? = rootCandidate.parent
        while (parentToken != null) {
            rootCandidate = transitionInfo.getChange(parentToken) ?: break
            parentToken = rootCandidate.parent
        }

        // Make sure nothing weird happened, like getChange() returning null.
        check(rootCandidate != null) { "Failed to find a root leash" }

        // Create a new floating view in Launcher, positioned above the launching icon
        val drawableArea = launchingIconView.iconDrawableArea
        val appIcon = appInfo.newIcon(launchingIconView.context)
        appIcon.setBounds(0, 0, dp.iconSizePx, dp.iconSizePx)

        val floatingView =
                FloatingAppPairView.getFloatingAppPairView(
                        launcher,
                        drawableArea,
                        appIcon,
                        null /*appIcon2*/,
                        0 /*dividerPos*/
                )
        floatingView.bringToFront()
        launchAnimation.play(
                getIconLaunchValueAnimator(t, dp, finishCallback, launcher, floatingView,
                        rootCandidate))
        launchAnimation.start()
    }

    private fun getIconLaunchValueAnimator(t: Transaction,
                                           dp: com.android.launcher3.DeviceProfile,
                                           finishCallback: Runnable,
                                           launcher: Launcher,
                                           floatingView: FloatingAppPairView,
                                           rootCandidate: Change) : ValueAnimator {
        val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
        val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
        progressUpdater.setDuration(timings.getDuration().toLong())
        progressUpdater.interpolator = Interpolators.LINEAR

        // Shell animation: the apps are revealed toward end of the launch animation
        progressUpdater.addUpdateListener { valueAnimator: ValueAnimator ->
            val progress =
                    Interpolators.clampToProgress(
                            Interpolators.LINEAR,
                            valueAnimator.animatedFraction,
                            timings.appRevealStartOffset,
                            timings.appRevealEndOffset
                    )

            // Set the alpha of the shell layer (2 apps + divider)
            t.setAlpha(rootCandidate.leash, progress)
            t.apply()
        }

        progressUpdater.addUpdateListener(
                object : MultiValueUpdateListener() {
                    var mDx =
@@ -775,8 +904,6 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
                    }
                }
        )

        // When animation ends, remove the floating view and run finishCallback
        progressUpdater.addListener(
                object : AnimatorListenerAdapter() {
                    override fun onAnimationEnd(animation: Animator) {
@@ -786,19 +913,21 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
                }
        )

        launchAnimation.play(progressUpdater)
        launchAnimation.start()
        return progressUpdater
    }

    /**
     * This is a scale-up-and-fade-in animation (34% to 100%) for launching an app in Overview when
     * there is no visible associated tile to expand from.
     * [windowingMode] helps determine whether we are looking for a split or a single fullscreen
     * [Change]
     */
    @VisibleForTesting
    fun composeScaleUpLaunchAnimation(
        transitionInfo: TransitionInfo,
        t: Transaction,
        finishCallback: Runnable
        finishCallback: Runnable,
        windowingMode: Int
    ) {
        val launchAnimation = AnimatorSet()
        val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
@@ -812,9 +941,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC

            // TODO (b/316490565): Replace this logic when SplitBounds is available to
            //  startAnimation() and we can know the precise taskIds of launching tasks.
            // Find a change that has WINDOWING_MODE_MULTI_WINDOW.
            if (
                taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW &&
                taskInfo.windowingMode == windowingMode &&
                    (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
            ) {
                // Found one!
+16 −15
Original line number Diff line number Diff line
@@ -37,17 +37,18 @@ import com.android.systemui.shared.system.QuickStepContract
 * animation. Consists of a rectangular background that splits into two, and two app icons that
 * increase in size during the animation.
 */
class FloatingAppPairBackground(
open class FloatingAppPairBackground(
        context: Context,
    private val floatingView: FloatingAppPairView, // the view that we will draw this background on
        // the view that we will draw this background on
        protected val floatingView: FloatingAppPairView,
        private val appIcon1: Drawable,
    private val appIcon2: Drawable,
        private val appIcon2: Drawable?,
        dividerPos: Int
) : Drawable() {
    companion object {
        // Design specs -- app icons start small and expand during the animation
        private val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
        private val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)
        internal val STARTING_ICON_SIZE_PX = Utilities.dpToPx(22f)
        internal val ENDING_ICON_SIZE_PX = Utilities.dpToPx(66f)

        // Null values to use with drawDoubleRoundRect(), since there doesn't seem to be any other
        // API for drawing rectangles with 4 different corner radii.
@@ -59,13 +60,13 @@ class FloatingAppPairBackground(
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)

    // Animation interpolators
    private val expandXInterpolator: Interpolator
    private val expandYInterpolator: Interpolator
    protected val expandXInterpolator: Interpolator
    protected val expandYInterpolator: Interpolator
    private val cellSplitInterpolator: Interpolator
    private val iconFadeInterpolator: Interpolator
    protected val iconFadeInterpolator: Interpolator

    // Device-specific measurements
    private val deviceCornerRadius: Float
    protected val deviceCornerRadius: Float
    private val deviceHalfDividerSize: Float
    private val desiredSplitRatio: Float

@@ -217,7 +218,7 @@ class FloatingAppPairBackground(
        canvas.save()
        canvas.translate(changingIcon2Left, changingIconTop)
        canvas.scale(changingIconScaleX, changingIconScaleY)
        appIcon2.alpha = changingIconAlpha
        appIcon2!!.alpha = changingIconAlpha
        appIcon2.draw(canvas)
        canvas.restore()
    }
@@ -317,7 +318,7 @@ class FloatingAppPairBackground(
        canvas.save()
        canvas.translate(changingIconLeft, changingIcon2Top)
        canvas.scale(changingIconScaleX, changingIconScaleY)
        appIcon2.alpha = changingIconAlpha
        appIcon2!!.alpha = changingIconAlpha
        appIcon2.draw(canvas)
        canvas.restore()
    }
@@ -330,7 +331,7 @@ class FloatingAppPairBackground(
     * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
     *   right y, bottom right x, and so on.
     */
    private fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
    protected fun drawCustomRoundedRect(c: Canvas, rect: RectF, radii: FloatArray) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // Canvas.drawDoubleRoundRect is supported from Q onward
            c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, backgroundPaint)
+12 −5
Original line number Diff line number Diff line
@@ -40,8 +40,8 @@ class FloatingAppPairView @JvmOverloads constructor(context: Context, attrs: Att
        fun getFloatingAppPairView(
            launcher: StatefulActivity<*>,
            originalView: View,
            appIcon1: Drawable,
            appIcon2: Drawable,
            appIcon1: Drawable?,
            appIcon2: Drawable?,
            dividerPos: Int
        ): FloatingAppPairView {
            val dragLayer: ViewGroup = launcher.getDragLayer()
@@ -64,8 +64,8 @@ class FloatingAppPairView @JvmOverloads constructor(context: Context, attrs: Att
    fun init(
        launcher: StatefulActivity<*>,
        originalView: View,
        appIcon1: Drawable,
        appIcon2: Drawable,
        appIcon1: Drawable?,
        appIcon2: Drawable?,
        dividerPos: Int
    ) {
        val viewBounds = Rect(0, 0, originalView.width, originalView.height)
@@ -92,7 +92,14 @@ class FloatingAppPairView @JvmOverloads constructor(context: Context, attrs: Att
        layoutParams = lp

        // Prepare to draw app pair icon background
        background = FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
        background = if (appIcon1 == null || appIcon2 == null) {
            val iconToAnimate = appIcon1 ?: appIcon2
            checkNotNull(iconToAnimate)
            FloatingFullscreenAppPairBackground(context, this, iconToAnimate,
                    dividerPos)
        } else {
            FloatingAppPairBackground(context, this, appIcon1, appIcon2, dividerPos)
        }
        background.setBounds(0, 0, lp.width, lp.height)
    }

+95 −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.views

import android.content.Context
import android.graphics.Canvas
import android.graphics.RectF
import android.graphics.drawable.Drawable

class FloatingFullscreenAppPairBackground(
        context: Context,
        floatingView: FloatingAppPairView,
        private val iconToLaunch: Drawable,
        dividerPos: Int) :
        FloatingAppPairBackground(
                context,
                floatingView,
                iconToLaunch,
                null /*appIcon2*/,
                dividerPos
) {

    /** Animates the background as if launching a fullscreen task. */
    override fun draw(canvas: Canvas) {
        val progress = floatingView.progress

        // Since the entire floating app pair surface is scaling up during this animation, we
        // scale down most of these drawn elements so that they appear the proper size on-screen.
        val scaleFactorX = floatingView.scaleX
        val scaleFactorY = floatingView.scaleY

        // Get the bounds where we will draw the background image
        val width = bounds.width().toFloat()
        val height = bounds.height().toFloat()

        // Get device-specific measurements
        val cornerRadiusX = deviceCornerRadius / scaleFactorX
        val cornerRadiusY = deviceCornerRadius / scaleFactorY

        // Draw background
        drawCustomRoundedRect(
                canvas,
                RectF(0f, 0f, width, height),
                floatArrayOf(
                        cornerRadiusX,
                        cornerRadiusY,
                        cornerRadiusX,
                        cornerRadiusY,
                        cornerRadiusX,
                        cornerRadiusY,
                        cornerRadiusX,
                        cornerRadiusY,
                )
        )

        // Calculate changing measurements for icon.
        val changingIconSizeX =
                (STARTING_ICON_SIZE_PX +
                        ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
                                expandXInterpolator.getInterpolation(progress))) / scaleFactorX
        val changingIconSizeY =
                (STARTING_ICON_SIZE_PX +
                        ((ENDING_ICON_SIZE_PX - STARTING_ICON_SIZE_PX) *
                                expandYInterpolator.getInterpolation(progress))) / scaleFactorY

        val changingIcon1Left = (width / 2f) - (changingIconSizeX / 2f)
        val changingIconTop = (height / 2f) - (changingIconSizeY / 2f)
        val changingIconScaleX = changingIconSizeX / iconToLaunch.bounds.width()
        val changingIconScaleY = changingIconSizeY / iconToLaunch.bounds.height()
        val changingIconAlpha =
                (255 - (255 * iconFadeInterpolator.getInterpolation(progress))).toInt()

        // Draw icon
        canvas.save()
        canvas.translate(changingIcon1Left, changingIconTop)
        canvas.scale(changingIconScaleX, changingIconScaleY)
        iconToLaunch.alpha = changingIconAlpha
        iconToLaunch.draw(canvas)
        canvas.restore()
    }
}
 No newline at end of file
+73 −4
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@

package com.android.quickstep.util

import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.view.ContextThemeWrapper
@@ -39,8 +41,10 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.kotlin.any
import org.mockito.kotlin.doNothing
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
@@ -255,6 +259,9 @@ class SplitAnimationControllerTest {
        doNothing()
            .whenever(spySplitAnimationController)
            .composeIconSplitLaunchAnimator(any(), any(), any(), any())
        doReturn(-1)
                .whenever(spySplitAnimationController)
                .hasChangesForBothAppPairs(any(), any())

        spySplitAnimationController.playSplitLaunchAnimation(
            null /* launchingTaskView */,
@@ -276,13 +283,74 @@ class SplitAnimationControllerTest {
    }

    @Test
    fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarContextCorrectly() {
    fun playsAppropriateSplitLaunchAnimation_playsIconFullscreenLaunchCorrectly() {
        val spySplitAnimationController = spy(splitAnimationController)
        whenever(mockAppPairIcon.context).thenReturn(mockContextThemeWrapper)
        doNothing()
                .whenever(spySplitAnimationController)
                .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), any())
        doReturn(0)
                .whenever(spySplitAnimationController)
                .hasChangesForBothAppPairs(any(), any())

        spySplitAnimationController.playSplitLaunchAnimation(
                null /* launchingTaskView */,
                mockAppPairIcon,
                taskId,
                taskId2,
                null /* apps */,
                null /* wallpapers */,
                null /* nonApps */,
                stateManager,
                depthController,
                transitionInfo,
                transaction,
                {} /* finishCallback */
        )

        verify(spySplitAnimationController)
                .composeFullscreenIconSplitLaunchAnimator(any(), any(), any(), any(), eq(0))
    }

    @Test
    fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarCMultiWindow() {
        val spySplitAnimationController = spy(splitAnimationController)
        whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
        doNothing()
            .whenever(spySplitAnimationController)
            .composeScaleUpLaunchAnimation(any(), any(), any())
            .composeScaleUpLaunchAnimation(any(), any(), any(), any())
        doReturn(-1)
                .whenever(spySplitAnimationController)
                .hasChangesForBothAppPairs(any(), any())
        spySplitAnimationController.playSplitLaunchAnimation(
            null /* launchingTaskView */,
            mockAppPairIcon,
            taskId,
            taskId2,
            null /* apps */,
            null /* wallpapers */,
            null /* nonApps */,
            stateManager,
            depthController,
            transitionInfo,
            transaction,
            {} /* finishCallback */
        )

        verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
                eq(WINDOWING_MODE_MULTI_WINDOW))
    }

    @Test
    fun playsAppropriateSplitLaunchAnimation_playsIconLaunchFromTaskbarFullscreen() {
        val spySplitAnimationController = spy(splitAnimationController)
        whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
        doNothing()
                .whenever(spySplitAnimationController)
                .composeScaleUpLaunchAnimation(any(), any(), any(), any())
        doReturn(0)
                .whenever(spySplitAnimationController)
                .hasChangesForBothAppPairs(any(), any())
        spySplitAnimationController.playSplitLaunchAnimation(
                null /* launchingTaskView */,
                mockAppPairIcon,
@@ -298,7 +366,8 @@ class SplitAnimationControllerTest {
                {} /* finishCallback */
        )

        verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any())
        verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
                eq(WINDOWING_MODE_FULLSCREEN))
    }

    @Test