Allow single root candidate for app pair launch for pip edge case

* Shell will launch single task if requested split apps have one of them
already in Pip
* Create a separate method to set animation for launching from the
appPair icon on workspace
* Reuse the animation method for launching an AppPair icon from taskbar
by specifying which windowing mode to look for if we're launching the
actual split pair vs just one in fullscreen

Bug: 323089902
Test: Launches fine visually
Change-Id: I415343a48e980afd7f4e511558d350cf15b97ca1
This commit is contained in:
Vinit Nayak
2024-04-25 16:32:42 -07:00
parent a06b40c1c4
commit 23b85ffdba
5 changed files with 416 additions and 116 deletions
@@ -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
@@ -50,6 +51,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.taskbar.TaskbarActivityContext
@@ -69,6 +71,7 @@ import com.android.quickstep.views.TaskThumbnailViewDeprecated
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
@@ -553,8 +556,14 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
check(info != null && t != null) {
"trying to launch an app pair icon, but encountered an unexpected null"
}
composeIconSplitLaunchAnimator(launchingIconView, info, t, finishCallback)
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) {
@@ -618,6 +627,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.
@@ -653,7 +695,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
}
@@ -663,11 +706,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) {
@@ -711,27 +749,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,
@@ -742,84 +766,189 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
)
floatingView.bringToFront()
// Launcher animation: animate the floating view, expanding to fill the display surface
progressUpdater.addUpdateListener(
object : MultiValueUpdateListener() {
var mDx =
FloatProp(
floatingView.startingPosition.left,
dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
Interpolators.clampToProgress(
timings.getStagedRectXInterpolator(),
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mDy =
FloatProp(
floatingView.startingPosition.top,
dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mScaleX =
FloatProp(
1f /* start */,
dp.widthPx / floatingView.startingPosition.width(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mScaleY =
FloatProp(
1f /* start */,
dp.heightPx / floatingView.startingPosition.height(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
override fun onUpdate(percent: Float, initOnly: Boolean) {
floatingView.progress = percent
floatingView.x = mDx.value
floatingView.y = mDy.value
floatingView.scaleX = mScaleX.value
floatingView.scaleY = mScaleY.value
floatingView.invalidate()
}
}
)
// When animation ends, remove the floating view and run finishCallback
progressUpdater.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
safeRemoveViewFromDragLayer(launcher, floatingView)
finishCallback.run()
}
}
)
launchAnimation.play(progressUpdater)
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 = QuickstepLauncher.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: QuickstepLauncher,
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 =
FloatProp(
floatingView.startingPosition.left,
dp.widthPx / 2f - floatingView.startingPosition.width() / 2f,
Interpolators.clampToProgress(
timings.getStagedRectXInterpolator(),
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mDy =
FloatProp(
floatingView.startingPosition.top,
dp.heightPx / 2f - floatingView.startingPosition.height() / 2f,
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mScaleX =
FloatProp(
1f /* start */,
dp.widthPx / floatingView.startingPosition.width(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
var mScaleY =
FloatProp(
1f /* start */,
dp.heightPx / floatingView.startingPosition.height(),
Interpolators.clampToProgress(
Interpolators.EMPHASIZED,
timings.stagedRectSlideStartOffset,
timings.stagedRectSlideEndOffset
)
)
override fun onUpdate(percent: Float, initOnly: Boolean) {
floatingView.progress = percent
floatingView.x = mDx.value
floatingView.y = mDy.value
floatingView.scaleX = mScaleX.value
floatingView.scaleY = mScaleY.value
floatingView.invalidate()
}
}
)
progressUpdater.addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
safeRemoveViewFromDragLayer(launcher, floatingView)
finishCallback.run()
}
}
)
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)
@@ -833,9 +962,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!
@@ -36,17 +36,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(
context: Context,
private val floatingView: FloatingAppPairView, // the view that we will draw this background on
private val appIcon1: Drawable,
private val appIcon2: Drawable,
dividerPos: Int
open class FloatingAppPairBackground(
context: Context,
// the view that we will draw this background on
protected val floatingView: FloatingAppPairView,
private val appIcon1: 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.
@@ -58,13 +59,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
@@ -214,7 +215,7 @@ class FloatingAppPairBackground(
canvas.save()
canvas.translate(changingIcon2Left, changingIconTop)
canvas.scale(changingIconScaleX, changingIconScaleY)
appIcon2.alpha = changingIconAlpha
appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -312,7 +313,7 @@ class FloatingAppPairBackground(
canvas.save()
canvas.translate(changingIconLeft, changingIcon2Top)
canvas.scale(changingIconScaleX, changingIconScaleY)
appIcon2.alpha = changingIconAlpha
appIcon2!!.alpha = changingIconAlpha
appIcon2.draw(canvas)
canvas.restore()
}
@@ -325,7 +326,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)
@@ -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)
}
@@ -0,0 +1,95 @@
/*
* 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()
}
}
@@ -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
@@ -273,6 +277,9 @@ class SplitAnimationControllerTest {
doNothing()
.whenever(spySplitAnimationController)
.composeIconSplitLaunchAnimator(any(), any(), any(), any())
doReturn(-1)
.whenever(spySplitAnimationController)
.hasChangesForBothAppPairs(any(), any())
spySplitAnimationController.playSplitLaunchAnimation(
null /* launchingTaskView */,
@@ -294,13 +301,45 @@ 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,
@@ -316,7 +355,37 @@ class SplitAnimationControllerTest {
{} /* finishCallback */
)
verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any())
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,
taskId,
taskId2,
null /* apps */,
null /* wallpapers */,
null /* nonApps */,
stateManager,
depthController,
transitionInfo,
transaction,
{} /* finishCallback */
)
verify(spySplitAnimationController).composeScaleUpLaunchAnimation(any(), any(), any(),
eq(WINDOWING_MODE_FULLSCREEN))
}
@Test