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

This commit is contained in:
Vinit Nayak
2024-05-03 06:30:43 +00:00
committed by Android (Google) Code Review
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
@@ -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"
}
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) {
@@ -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,84 +745,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 = 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 =
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)
@@ -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!
@@ -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(
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.
@@ -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)
@@ -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
@@ -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,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,
@@ -298,7 +337,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