Merge changes Ia5accb74,Ic4d7def4,I30bd56fc into sc-v2-dev am: 7339a87bf3

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16357603

Change-Id: I31d6bbad7f8c03f58ba8b6193aba944f9cdccbbf
This commit is contained in:
Jordan Demeulenaere
2021-12-07 10:34:12 +00:00
committed by Automerger Merge Worker
12 changed files with 253 additions and 174 deletions

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:pathData="M 0, 0 C 0.1217, 0.0462, 0.15, 0.4686, 0.1667, 0.66 C 0.1834, 0.8878, 0.1667, 1, 1, 1" />

View File

@@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 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.
-->
<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
android:pathData="M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1" />

View File

@@ -21,6 +21,7 @@ import android.app.ActivityTaskManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.TaskInfo import android.app.TaskInfo
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.Path
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.os.Looper import android.os.Looper
@@ -34,6 +35,7 @@ import android.view.SyncRtSurfaceTransactionApplier
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils import com.android.internal.policy.ScreenDecorationsUtils
@@ -45,16 +47,46 @@ private const val TAG = "ActivityLaunchAnimator"
* A class that allows activities to be started in a seamless way from a view that is transforming * A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window. * nicely into the starting window.
*/ */
class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) { class ActivityLaunchAnimator(
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS)
) {
companion object { companion object {
@JvmField
val TIMINGS = LaunchAnimator.Timings(
totalDuration = 500L,
contentBeforeFadeOutDelay = 0L,
contentBeforeFadeOutDuration = 150L,
contentAfterFadeInDelay = 150L,
contentAfterFadeInDuration = 183L
)
val INTERPOLATORS = LaunchAnimator.Interpolators(
positionInterpolator = Interpolators.EMPHASIZED,
positionXInterpolator = createPositionXInterpolator(),
contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
)
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
private const val ANIMATION_DELAY_NAV_FADE_IN = private val ANIMATION_DELAY_NAV_FADE_IN =
LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN TIMINGS.totalDuration - ANIMATION_DURATION_NAV_FADE_IN
private val NAV_FADE_IN_INTERPOLATOR = Interpolators.STANDARD_DECELERATE
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
/** The time we wait before timing out the remote animation after starting the intent. */
private const val LAUNCH_TIMEOUT = 1000L private const val LAUNCH_TIMEOUT = 1000L
private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) private fun createPositionXInterpolator(): Interpolator {
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) val path = Path().apply {
moveTo(0f, 0f)
cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
}
return PathInterpolator(path)
}
} }
/** /**
@@ -107,8 +139,8 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
val animationAdapter = if (!hideKeyguardWithAnimation) { val animationAdapter = if (!hideKeyguardWithAnimation) {
RemoteAnimationAdapter( RemoteAnimationAdapter(
runner, runner,
LaunchAnimator.ANIMATION_DURATION, TIMINGS.totalDuration,
LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */ TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
) )
} else { } else {
null null
@@ -448,7 +480,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
state: LaunchAnimator.State, state: LaunchAnimator.State,
linearProgress: Float linearProgress: Float
) { ) {
val fadeInProgress = LaunchAnimator.getProgress(linearProgress, val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT) ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
@@ -463,7 +495,7 @@ class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
.withWindowCrop(windowCrop) .withWindowCrop(windowCrop)
.withVisibility(true) .withVisibility(true)
} else { } else {
val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0, val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
ANIMATION_DURATION_NAV_FADE_OUT) ANIMATION_DURATION_NAV_FADE_OUT)
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
} }

View File

@@ -20,7 +20,6 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.app.Dialog import android.app.Dialog
import android.content.Context
import android.graphics.Color import android.graphics.Color
import android.graphics.Rect import android.graphics.Rect
import android.os.Looper import android.os.Looper
@@ -28,10 +27,11 @@ import android.service.dreams.IDreamManager
import android.util.Log import android.util.Log
import android.util.MathUtils import android.util.MathUtils
import android.view.GhostView import android.view.GhostView
import android.view.SurfaceControl
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewTreeObserver.OnPreDrawListener import android.view.ViewRootImpl
import android.view.WindowManager import android.view.WindowManager
import android.widget.FrameLayout import android.widget.FrameLayout
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -42,12 +42,20 @@ private const val TAG = "DialogLaunchAnimator"
* A class that allows dialogs to be started in a seamless way from a view that is transforming * A class that allows dialogs to be started in a seamless way from a view that is transforming
* nicely into the starting dialog. * nicely into the starting dialog.
*/ */
class DialogLaunchAnimator( class DialogLaunchAnimator @JvmOverloads constructor(
private val context: Context, private val dreamManager: IDreamManager,
private val launchAnimator: LaunchAnimator, private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
private val dreamManager: IDreamManager private var isForTesting: Boolean = false
) { ) {
private companion object { private companion object {
private val TIMINGS = ActivityLaunchAnimator.TIMINGS
// We use the same interpolator for X and Y axis to make sure the dialog does not move out
// of the screen bounds during the animation.
private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy(
positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
)
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.launch_animation_running
} }
@@ -96,14 +104,14 @@ class DialogLaunchAnimator(
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true) animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
val animatedDialog = AnimatedDialog( val animatedDialog = AnimatedDialog(
context,
launchAnimator, launchAnimator,
dreamManager, dreamManager,
animateFrom, animateFrom,
onDialogDismissed = { openedDialogs.remove(it) }, onDialogDismissed = { openedDialogs.remove(it) },
dialog = dialog, dialog = dialog,
animateBackgroundBoundsChange, animateBackgroundBoundsChange,
animatedParent animatedParent,
isForTesting
) )
openedDialogs.add(animatedDialog) openedDialogs.add(animatedDialog)
@@ -157,7 +165,6 @@ class DialogLaunchAnimator(
} }
private class AnimatedDialog( private class AnimatedDialog(
private val context: Context,
private val launchAnimator: LaunchAnimator, private val launchAnimator: LaunchAnimator,
private val dreamManager: IDreamManager, private val dreamManager: IDreamManager,
@@ -174,10 +181,16 @@ private class AnimatedDialog(
val dialog: Dialog, val dialog: Dialog,
/** Whether we should animate the dialog background when its bounds change. */ /** Whether we should animate the dialog background when its bounds change. */
private val animateBackgroundBoundsChange: Boolean, animateBackgroundBoundsChange: Boolean,
/** Launch animation corresponding to the parent [AnimatedDialog]. */ /** Launch animation corresponding to the parent [AnimatedDialog]. */
private val parentAnimatedDialog: AnimatedDialog? = null private val parentAnimatedDialog: AnimatedDialog? = null,
/**
* Whether we are currently running in a test, in which case we need to disable
* synchronization.
*/
private val isForTesting: Boolean
) { ) {
/** /**
* The DecorView of this dialog window. * The DecorView of this dialog window.
@@ -266,14 +279,14 @@ private class AnimatedDialog(
// and the view that we added so that we can dismiss the dialog when this view is // and the view that we added so that we can dismiss the dialog when this view is
// clicked. This is necessary because DecorView overrides onTouchEvent and therefore we // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
// can't set the click listener directly on the (now fullscreen) DecorView. // can't set the click listener directly on the (now fullscreen) DecorView.
val fullscreenTransparentBackground = FrameLayout(context) val fullscreenTransparentBackground = FrameLayout(dialog.context)
decorView.addView( decorView.addView(
fullscreenTransparentBackground, fullscreenTransparentBackground,
0 /* index */, 0 /* index */,
FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT) FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
) )
val dialogContentWithBackground = FrameLayout(context) val dialogContentWithBackground = FrameLayout(dialog.context)
dialogContentWithBackground.background = decorView.background dialogContentWithBackground.background = decorView.background
// Make the window background transparent. Note that setting the window (or DecorView) // Make the window background transparent. Note that setting the window (or DecorView)
@@ -365,59 +378,77 @@ private class AnimatedDialog(
// Show the dialog. // Show the dialog.
dialog.show() dialog.show()
// Add a temporary touch surface ghost as soon as the window is ready to draw. This addTouchSurfaceGhost()
// temporary ghost will be drawn together with the touch surface, but in the dialog }
// window. Once it is drawn, we will make the touch surface invisible, and then start the
// animation. We do all this synchronization to avoid flicker that would occur if we made private fun addTouchSurfaceGhost() {
// the touch surface invisible too early (before its ghost is drawn), leading to one or more if (decorView.viewRootImpl == null) {
// frames with a hole instead of the touch surface (or its ghost). // Make sure that we have access to the dialog view root to synchronize the creation of
decorView.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener { // the ghost.
override fun onPreDraw(): Boolean { decorView.post(::addTouchSurfaceGhost)
decorView.viewTreeObserver.removeOnPreDrawListener(this) return
addTemporaryTouchSurfaceGhost()
return true
}
})
decorView.invalidate()
} }
private fun addTemporaryTouchSurfaceGhost() {
// Create a ghost of the touch surface (which will make the touch surface invisible) and add // Create a ghost of the touch surface (which will make the touch surface invisible) and add
// it to the dialog. We will wait for this ghost to be drawn before starting the animation. // it to the host dialog. We trigger a one off synchronization to make sure that this is
val ghost = GhostView.addGhost(touchSurface, decorView) // done in sync between the two different windows.
synchronizeNextDraw(then = {
// The ghost of the touch surface was just created, so the touch surface was made invisible.
// We make it visible again until the ghost is actually drawn.
touchSurface.visibility = View.VISIBLE
// Wait for the ghost to be drawn before continuing.
ghost.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
override fun onPreDraw(): Boolean {
ghost.viewTreeObserver.removeOnPreDrawListener(this)
onTouchSurfaceGhostDrawn()
return true
}
})
ghost.invalidate()
}
private fun onTouchSurfaceGhostDrawn() {
// Make the touch surface invisible and make sure that it stays invisible as long as the
// dialog is shown or animating.
touchSurface.visibility = View.INVISIBLE
(touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
// Add a pre draw listener to (maybe) start the animation once the touch surface is
// actually invisible.
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
override fun onPreDraw(): Boolean {
touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
isTouchSurfaceGhostDrawn = true isTouchSurfaceGhostDrawn = true
maybeStartLaunchAnimation() maybeStartLaunchAnimation()
return true
}
}) })
touchSurface.invalidate() GhostView.addGhost(touchSurface, decorView)
// The ghost of the touch surface was just created, so the touch surface is currently
// invisible. We need to make sure that it stays invisible as long as the dialog is shown or
// animating.
(touchSurface as? LaunchableView)?.setShouldBlockVisibilityChanges(true)
}
/**
* Synchronize the next draw of the touch surface and dialog view roots so that they are
* performed at the same time, in the same transaction. This is necessary to make sure that the
* ghost of the touch surface is drawn at the same time as the touch surface is made invisible
* (or inversely, removed from the UI when the touch surface is made visible).
*/
private fun synchronizeNextDraw(then: () -> Unit) {
if (isForTesting || !touchSurface.isAttachedToWindow || touchSurface.viewRootImpl == null ||
!decorView.isAttachedToWindow || decorView.viewRootImpl == null) {
// No need to synchronize if either the touch surface or dialog view is not attached
// to a window.
then()
return
}
// Consume the next frames of both view roots to make sure the ghost view is drawn at
// exactly the same time as when the touch surface is made invisible.
var remainingTransactions = 0
val mergedTransactions = SurfaceControl.Transaction()
fun onTransaction(transaction: SurfaceControl.Transaction?) {
remainingTransactions--
transaction?.let { mergedTransactions.merge(it) }
if (remainingTransactions == 0) {
mergedTransactions.apply()
then()
}
}
fun consumeNextDraw(viewRootImpl: ViewRootImpl) {
if (viewRootImpl.consumeNextDraw(::onTransaction)) {
remainingTransactions++
// Make sure we trigger a traversal.
viewRootImpl.view.invalidate()
}
}
consumeNextDraw(touchSurface.viewRootImpl)
consumeNextDraw(decorView.viewRootImpl)
if (remainingTransactions == 0) {
then()
}
} }
private fun findFirstViewGroupWithBackground(view: View): ViewGroup? { private fun findFirstViewGroupWithBackground(view: View): ViewGroup? {
@@ -483,7 +514,7 @@ private class AnimatedDialog(
private fun onDialogDismissed() { private fun onDialogDismissed() {
if (Looper.myLooper() != Looper.getMainLooper()) { if (Looper.myLooper() != Looper.getMainLooper()) {
context.mainExecutor.execute { onDialogDismissed() } dialog.context.mainExecutor.execute { onDialogDismissed() }
return return
} }
@@ -556,25 +587,12 @@ private class AnimatedDialog(
.removeOnLayoutChangeListener(backgroundLayoutListener) .removeOnLayoutChangeListener(backgroundLayoutListener)
} }
// The animated ghost was just removed. We create a temporary ghost that will be // Make sure that the removal of the ghost and making the touch surface visible is
// removed only once we draw the touch surface, to avoid flickering that would // done at the same time.
// happen when removing the ghost too early (before the touch surface is drawn). synchronizeNextDraw(then = {
GhostView.addGhost(touchSurface, decorView)
touchSurface.viewTreeObserver.addOnPreDrawListener(object : OnPreDrawListener {
override fun onPreDraw(): Boolean {
touchSurface.viewTreeObserver.removeOnPreDrawListener(this)
// Now that the touch surface was drawn, we can remove the temporary ghost
// and instantly dismiss the dialog.
GhostView.removeGhost(touchSurface)
onAnimationFinished(true /* instantDismiss */) onAnimationFinished(true /* instantDismiss */)
onDialogDismissed(this@AnimatedDialog) onDialogDismissed(this@AnimatedDialog)
return true
}
}) })
touchSurface.invalidate()
} }
) )
} }

View File

@@ -27,25 +27,19 @@ import android.util.Log
import android.util.MathUtils import android.util.MathUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.AnimationUtils import android.view.animation.Interpolator
import android.view.animation.PathInterpolator import com.android.systemui.animation.Interpolators.LINEAR
import kotlin.math.roundToInt import kotlin.math.roundToInt
private const val TAG = "LaunchAnimator" private const val TAG = "LaunchAnimator"
/** A base class to animate a window launch (activity or dialog) from a view . */ /** A base class to animate a window launch (activity or dialog) from a view . */
class LaunchAnimator @JvmOverloads constructor( class LaunchAnimator(
context: Context, private val timings: Timings,
private val isForTesting: Boolean = false private val interpolators: Interpolators
) { ) {
companion object { companion object {
internal const val DEBUG = false internal const val DEBUG = false
const val ANIMATION_DURATION = 500L
private const val ANIMATION_DURATION_FADE_OUT_CONTENT = 150L
private const val ANIMATION_DURATION_FADE_IN_WINDOW = 183L
private const val ANIMATION_DELAY_FADE_IN_WINDOW = ANIMATION_DURATION_FADE_OUT_CONTENT
private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
/** /**
@@ -53,23 +47,20 @@ class LaunchAnimator @JvmOverloads constructor(
* sub-animation starting [delay] ms after the launch animation and that lasts [duration]. * sub-animation starting [delay] ms after the launch animation and that lasts [duration].
*/ */
@JvmStatic @JvmStatic
fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float { fun getProgress(
timings: Timings,
linearProgress: Float,
delay: Long,
duration: Long
): Float {
return MathUtils.constrain( return MathUtils.constrain(
(linearProgress * ANIMATION_DURATION - delay) / duration, (linearProgress * timings.totalDuration - delay) / duration,
0.0f, 0.0f,
1.0f 1.0f
) )
} }
} }
/** The interpolator used for the width, height, Y position and corner radius. */
private val animationInterpolator = AnimationUtils.loadInterpolator(context,
R.interpolator.launch_animation_interpolator_y)
/** The interpolator used for the X position. */
private val animationInterpolatorX = AnimationUtils.loadInterpolator(context,
R.interpolator.launch_animation_interpolator_x)
private val launchContainerLocation = IntArray(2) private val launchContainerLocation = IntArray(2)
private val cornerRadii = FloatArray(8) private val cornerRadii = FloatArray(8)
@@ -159,6 +150,45 @@ class LaunchAnimator @JvmOverloads constructor(
fun cancel() fun cancel()
} }
/** The timings (durations and delays) used by this animator. */
class Timings(
/** The total duration of the animation. */
val totalDuration: Long,
/** The time to wait before fading out the expanding content. */
val contentBeforeFadeOutDelay: Long,
/** The duration of the expanding content fade out. */
val contentBeforeFadeOutDuration: Long,
/**
* The time to wait before fading in the expanded content (usually an activity or dialog
* window).
*/
val contentAfterFadeInDelay: Long,
/** The duration of the expanded content fade in. */
val contentAfterFadeInDuration: Long
)
/** The interpolators used by this animator. */
data class Interpolators(
/** The interpolator used for the Y position, width, height and corner radius. */
val positionInterpolator: Interpolator,
/**
* The interpolator used for the X position. This can be different than
* [positionInterpolator] to create an arc-path during the animation.
*/
val positionXInterpolator: Interpolator = positionInterpolator,
/** The interpolator used when fading out the expanding content. */
val contentBeforeFadeOutInterpolator: Interpolator,
/** The interpolator used when fading in the expanded content. */
val contentAfterFadeInInterpolator: Interpolator
)
/** /**
* Start a launch animation controlled by [controller] towards [endState]. An intermediary * Start a launch animation controlled by [controller] towards [endState]. An intermediary
* layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
@@ -221,8 +251,8 @@ class LaunchAnimator @JvmOverloads constructor(
// Update state. // Update state.
val animator = ValueAnimator.ofFloat(0f, 1f) val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = if (isForTesting) 0 else ANIMATION_DURATION animator.duration = timings.totalDuration
animator.interpolator = Interpolators.LINEAR animator.interpolator = LINEAR
val launchContainerOverlay = launchContainer.overlay val launchContainerOverlay = launchContainer.overlay
var cancelled = false var cancelled = false
@@ -260,8 +290,8 @@ class LaunchAnimator @JvmOverloads constructor(
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non // TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
// reversed animation. // reversed animation.
val linearProgress = animation.animatedFraction val linearProgress = animation.animatedFraction
val progress = animationInterpolator.getInterpolation(linearProgress) val progress = interpolators.positionInterpolator.getInterpolation(linearProgress)
val xProgress = animationInterpolatorX.getInterpolation(linearProgress) val xProgress = interpolators.positionXInterpolator.getInterpolation(linearProgress)
val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress) val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
@@ -278,7 +308,12 @@ class LaunchAnimator @JvmOverloads constructor(
// The expanding view can/should be hidden once it is completely covered by the opening // The expanding view can/should be hidden once it is completely covered by the opening
// window. // window.
state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1 state.visible = getProgress(
timings,
linearProgress,
timings.contentBeforeFadeOutDelay,
timings.contentBeforeFadeOutDuration
) < 1
applyStateToWindowBackgroundLayer( applyStateToWindowBackgroundLayer(
windowBackgroundLayer, windowBackgroundLayer,
@@ -337,14 +372,25 @@ class LaunchAnimator @JvmOverloads constructor(
// We first fade in the background layer to hide the expanding view, then fade it out // We first fade in the background layer to hide the expanding view, then fade it out
// with SRC mode to draw a hole punch in the status bar and reveal the opening window. // with SRC mode to draw a hole punch in the status bar and reveal the opening window.
val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) val fadeInProgress = getProgress(
timings,
linearProgress,
timings.contentBeforeFadeOutDelay,
timings.contentBeforeFadeOutDuration
)
if (fadeInProgress < 1) { if (fadeInProgress < 1) {
val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress) val alpha =
interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt() drawable.alpha = (alpha * 0xFF).roundToInt()
} else { } else {
val fadeOutProgress = getProgress( val fadeOutProgress = getProgress(
linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW) timings,
val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress) linearProgress,
timings.contentAfterFadeInDelay,
timings.contentAfterFadeInDuration
)
val alpha =
1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt() drawable.alpha = (alpha * 0xFF).roundToInt()
if (drawHole) { if (drawHole) {

View File

@@ -25,7 +25,6 @@ import android.service.dreams.IDreamManager;
import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.DialogLaunchAnimator; import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
@@ -301,24 +300,15 @@ public interface StatusBarDependenciesModule {
*/ */
@Provides @Provides
@SysUISingleton @SysUISingleton
static LaunchAnimator provideLaunchAnimator(Context context) { static ActivityLaunchAnimator provideActivityLaunchAnimator() {
return new LaunchAnimator(context); return new ActivityLaunchAnimator();
} }
/** /**
*/ */
@Provides @Provides
@SysUISingleton @SysUISingleton
static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) { static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
return new ActivityLaunchAnimator(launchAnimator); return new DialogLaunchAnimator(dreamManager);
}
/**
*/
@Provides
@SysUISingleton
static DialogLaunchAnimator provideDialogLaunchAnimator(Context context,
LaunchAnimator launchAnimator, IDreamManager dreamManager) {
return new DialogLaunchAnimator(context, launchAnimator, dreamManager);
} }
} }

View File

@@ -2,6 +2,7 @@ package com.android.systemui.statusbar.notification
import android.util.MathUtils import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.LaunchAnimator import com.android.systemui.animation.LaunchAnimator
import kotlin.math.min import kotlin.math.min
@@ -55,6 +56,7 @@ class ExpandAnimationParameters(
} }
fun getProgress(delay: Long, duration: Long): Float { fun getProgress(delay: Long, duration: Long): Float {
return LaunchAnimator.getProgress(linearProgress, delay, duration) return LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS, linearProgress, delay,
duration)
} }
} }

View File

@@ -110,6 +110,7 @@ import com.android.keyguard.dagger.KeyguardUserSwitcherComponent;
import com.android.systemui.DejankUtils; import com.android.systemui.DejankUtils;
import com.android.systemui.Dependency; import com.android.systemui.Dependency;
import com.android.systemui.R; import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators; import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator; import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController; import com.android.systemui.biometrics.AuthController;
@@ -225,7 +226,8 @@ public class NotificationPanelViewController extends PanelViewController {
*/ */
private static final int FLING_HIDE = 2; private static final int FLING_HIDE = 2;
private static final long ANIMATION_DELAY_ICON_FADE_IN = private static final long ANIMATION_DELAY_ICON_FADE_IN =
LaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION ActivityLaunchAnimator.TIMINGS.getTotalDuration()
- CollapsedStatusBarFragment.FADE_IN_DURATION
- CollapsedStatusBarFragment.FADE_IN_DELAY - 48; - CollapsedStatusBarFragment.FADE_IN_DELAY - 48;
private final DozeParameters mDozeParameters; private final DozeParameters mDozeParameters;
@@ -3629,8 +3631,8 @@ public class NotificationPanelViewController extends PanelViewController {
} }
public void applyLaunchAnimationProgress(float linearProgress) { public void applyLaunchAnimationProgress(float linearProgress) {
boolean hideIcons = LaunchAnimator.getProgress(linearProgress, boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f; linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
if (hideIcons != mHideIconsDuringLaunchAnimation) { if (hideIcons != mHideIconsDuringLaunchAnimation) {
mHideIconsDuringLaunchAnimation = hideIcons; mHideIconsDuringLaunchAnimation = hideIcons;
if (!hideIcons) { if (!hideIcons) {

View File

@@ -23,7 +23,8 @@ class StatusBarLaunchAnimatorController(
delegate.onLaunchAnimationStart(isExpandingFullyAbove) delegate.onLaunchAnimationStart(isExpandingFullyAbove)
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true) statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) { if (!isExpandingFullyAbove) {
statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt()) statusBar.collapsePanelWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
} }
} }

View File

@@ -46,7 +46,7 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper @RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() { class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val launchContainer = LinearLayout(mContext) private val launchContainer = LinearLayout(mContext)
private val launchAnimator = LaunchAnimator(mContext, isForTesting = true) private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
@Mock lateinit var callback: ActivityLaunchAnimator.Callback @Mock lateinit var callback: ActivityLaunchAnimator.Callback
@Spy private val controller = TestLaunchAnimatorController(launchContainer) @Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback

View File

@@ -33,7 +33,7 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
class DialogLaunchAnimatorTest : SysuiTestCase() { class DialogLaunchAnimatorTest : SysuiTestCase() {
private val launchAnimator = LaunchAnimator(context, isForTesting = true) private val launchAnimator = LaunchAnimator(TEST_TIMINGS, TEST_INTERPOLATORS)
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
private val attachedViews = mutableSetOf<View>() private val attachedViews = mutableSetOf<View>()
@@ -42,7 +42,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
@Before @Before
fun setUp() { fun setUp() {
dialogLaunchAnimator = DialogLaunchAnimator(context, launchAnimator, dreamManager) dialogLaunchAnimator = DialogLaunchAnimator(
dreamManager, launchAnimator, isForTesting = true)
} }
@After @After

View File

@@ -0,0 +1,23 @@
package com.android.systemui.animation
/**
* A [LaunchAnimator.Timings] to be used in tests.
*
* Note that all timings except the total duration are non-zero to avoid divide-by-zero exceptions
* when computing the progress of a sub-animation (the contents fade in/out).
*/
val TEST_TIMINGS = LaunchAnimator.Timings(
totalDuration = 0L,
contentBeforeFadeOutDelay = 1L,
contentBeforeFadeOutDuration = 1L,
contentAfterFadeInDelay = 1L,
contentAfterFadeInDuration = 1L
)
/** A [LaunchAnimator.Interpolators] to be used in tests. */
val TEST_INTERPOLATORS = LaunchAnimator.Interpolators(
positionInterpolator = Interpolators.STANDARD,
positionXInterpolator = Interpolators.STANDARD,
contentBeforeFadeOutInterpolator = Interpolators.STANDARD,
contentAfterFadeInInterpolator = Interpolators.STANDARD
)