Split ActivityLaunchAnimator (1/2)

This CL splits ActivityLaunchAnimator by extracting common logic into
LaunchAnimator. The goal of this CL is to be able to reuse the
LaunchAnimator logic for dialog launches.

This CL also improves GhostedViewLaunchAnimator such that it also works
with:
 * launch containers that are not located at (0, 0).
 * ghosted views that move.

Test: atest ActivityLaunchAnimatorTest
Bug: 193634619
Change-Id: Ic05603600d4a7d4e27182c79bef54286087d00d0
This commit is contained in:
Jordan Demeulenaere
2021-08-24 16:29:52 +02:00
parent a83e90e0d0
commit c0a85b7c3b
15 changed files with 589 additions and 352 deletions

View File

@@ -165,6 +165,10 @@ public final class MathUtils {
return start + (stop - start) * amount;
}
public static float lerp(int start, int stop, float amount) {
return lerp((float) start, (float) stop, amount);
}
/**
* Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
* #lerp}{@code (a, b, s)}

View File

@@ -1,23 +1,31 @@
/*
* 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.
*/
package com.android.systemui.animation
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.app.ActivityManager
import android.app.ActivityTaskManager
import android.app.PendingIntent
import android.app.TaskInfo
import android.content.Context
import android.graphics.Matrix
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.GradientDrawable
import android.os.Looper
import android.os.RemoteException
import android.util.Log
import android.util.MathUtils
import android.view.IRemoteAnimationFinishedCallback
import android.view.IRemoteAnimationRunner
import android.view.RemoteAnimationAdapter
@@ -26,7 +34,6 @@ import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.policy.ScreenDecorationsUtils
@@ -38,52 +45,23 @@ private const val TAG = "ActivityLaunchAnimator"
* A class that allows activities to be started in a seamless way from a view that is transforming
* nicely into the starting window.
*/
class ActivityLaunchAnimator(
private val callback: Callback,
context: Context
) {
class ActivityLaunchAnimator(private val launchAnimator: LaunchAnimator) {
companion object {
private 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 const val ANIMATION_DURATION_NAV_FADE_IN = 266L
private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L
private const val ANIMATION_DELAY_NAV_FADE_IN =
ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
LaunchAnimator.ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN
private const val LAUNCH_TIMEOUT = 1000L
@JvmField val CONTENT_FADE_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
private val WINDOW_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0.6f, 1f)
private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
/**
* Given the [linearProgress] of a launch animation, return the linear progress of the
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
*/
@JvmStatic
fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
return MathUtils.constrain(
(linearProgress * ANIMATION_DURATION - delay) / duration,
0.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 cornerRadii = FloatArray(8)
/**
* The callback of this animator. This should be set before any call to
* [start(Pending)IntentWithAnimation].
*/
var callback: Callback? = null
/**
* Start an intent and animate the opening window. The intent will be started by running
@@ -119,6 +97,8 @@ class ActivityLaunchAnimator(
return
}
val callback = this.callback ?: throw IllegalStateException(
"ActivityLaunchAnimator.callback must be set before using this animator")
val runner = Runner(controller)
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
@@ -126,9 +106,9 @@ class ActivityLaunchAnimator(
// keyguard with the animation
val animationAdapter = if (!hideKeyguardWithAnimation) {
RemoteAnimationAdapter(
runner,
ANIMATION_DURATION,
ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
runner,
LaunchAnimator.ANIMATION_DURATION,
LaunchAnimator.ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
)
} else {
null
@@ -150,10 +130,10 @@ class ActivityLaunchAnimator(
// Only animate if the app is not already on top and will be opened, unless we are on the
// keyguard.
val willAnimate =
launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS ||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
hideKeyguardWithAnimation)
launchResult == ActivityManager.START_TASK_TO_FRONT ||
launchResult == ActivityManager.START_SUCCESS ||
(launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
hideKeyguardWithAnimation)
Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " +
"hideKeyguardWithAnimation=$hideKeyguardWithAnimation")
@@ -234,7 +214,7 @@ class ActivityLaunchAnimator(
*
* Note that all callbacks (onXXX methods) are all called on the main thread.
*/
interface Controller {
interface Controller : LaunchAnimator.Controller {
companion object {
/**
* Return a [Controller] that will animate and expand [view] into the opening window.
@@ -258,53 +238,12 @@ class ActivityLaunchAnimator(
}
}
/**
* The container in which the view that started the intent will be animating together with
* the opening window.
*
* This will be used to:
* - Get the associated [Context].
* - Compute whether we are expanding fully above the current window.
* - Apply surface transactions in sync with RenderThread.
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
* animation.
*/
var launchContainer: ViewGroup
/**
* Return the [State] of the view that will be animated. We will animate from this state to
* the final window state.
*
* Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
* animation.
*/
fun createAnimatorState(): State
/**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
fun onIntentStarted(willAnimate: Boolean) {}
/**
* The animation started. This is typically used to initialize any additional resource
* needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
* fully above the [root view][getRootView].
*/
fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
/** The animation made progress and the expandable view [state] should be updated. */
fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
/**
* The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
* called previously. This is typically used to clean up the resources initialized when the
* animation was started.
*/
fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
/**
* The animation was cancelled. Note that [onLaunchAnimationEnd] will still be called after
* this if the animation was already started, i.e. if [onLaunchAnimationStart] was called
@@ -313,66 +252,11 @@ class ActivityLaunchAnimator(
fun onLaunchAnimationCancelled() {}
}
/** The state of an expandable view during an [ActivityLaunchAnimator] animation. */
open class State(
/** The position of the view in screen space coordinates. */
var top: Int,
var bottom: Int,
var left: Int,
var right: Int,
var topCornerRadius: Float = 0f,
var bottomCornerRadius: Float = 0f
) {
private val startTop = top
private val startBottom = bottom
private val startLeft = left
private val startRight = right
private val startWidth = width
private val startHeight = height
val startCenterX = centerX
val startCenterY = centerY
val width: Int
get() = right - left
val height: Int
get() = bottom - top
open val topChange: Int
get() = top - startTop
open val bottomChange: Int
get() = bottom - startBottom
val leftChange: Int
get() = left - startLeft
val rightChange: Int
get() = right - startRight
val widthRatio: Float
get() = width.toFloat() / startWidth
val heightRatio: Float
get() = height.toFloat() / startHeight
val centerX: Float
get() = left + width / 2f
val centerY: Float
get() = top + height / 2f
/** Whether the expanded view should be visible or hidden. */
var visible: Boolean = true
}
@VisibleForTesting
inner class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
private val launchContainer = controller.launchContainer
private val context = launchContainer.context
private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer)
private var animator: ValueAnimator? = null
private val matrix = Matrix()
private val invertMatrix = Matrix()
@@ -380,6 +264,7 @@ class ActivityLaunchAnimator(
private var windowCropF = RectF()
private var timedOut = false
private var cancelled = false
private var animation: LaunchAnimator.Animation? = null
// A timeout to cancel the remote animation if it is not started within X milliseconds after
// the intent was started.
@@ -429,7 +314,7 @@ class ActivityLaunchAnimator(
nonApps: Array<out RemoteAnimationTarget>?,
iCallback: IRemoteAnimationFinishedCallback?
) {
if (DEBUG) {
if (LaunchAnimator.DEBUG) {
Log.d(TAG, "Remote animation started")
}
@@ -449,36 +334,20 @@ class ActivityLaunchAnimator(
it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
}
// Start state.
val state = controller.createAnimatorState()
val startTop = state.top
val startBottom = state.bottom
val startLeft = state.left
val startRight = state.right
val startXCenter = (startLeft + startRight) / 2f
val startWidth = startRight - startLeft
val startTopCornerRadius = state.topCornerRadius
val startBottomCornerRadius = state.bottomCornerRadius
// End state.
val windowBounds = window.screenSpaceBounds
val endTop = windowBounds.top
val endBottom = windowBounds.bottom
val endLeft = windowBounds.left
val endRight = windowBounds.right
val endXCenter = (endLeft + endRight) / 2f
val endWidth = endRight - endLeft
val endState = LaunchAnimator.State(
top = windowBounds.top,
bottom = windowBounds.bottom,
left = windowBounds.left,
right = windowBounds.right
)
val callback = this@ActivityLaunchAnimator.callback!!
val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
// TODO(b/184121838): Ensure that we are launching on the same screen.
val rootViewLocation = launchContainer.locationOnScreen
val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
endBottom >= rootViewLocation[1] + launchContainer.height &&
endLeft <= rootViewLocation[0] &&
endRight >= rootViewLocation[0] + launchContainer.width
// TODO(b/184121838): We should somehow get the top and bottom radius of the window.
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
// instead of recomputing isExpandingFullyAbove here.
val isExpandingFullyAbove =
launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
val endRadius = if (isExpandingFullyAbove) {
// Most of the time, expanding fully above the root view means expanding in full
// screen.
@@ -488,97 +357,40 @@ class ActivityLaunchAnimator(
// a radius of 0.
0f
}
endState.topCornerRadius = endRadius
endState.bottomCornerRadius = endRadius
// We add an extra layer with the same color as the app splash screen background color,
// which is usually the same color of the app background. We first fade in this layer
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
// launch container and reveal the opening window.
val windowBackgroundColor = callback.getBackgroundColor(window.taskInfo)
val windowBackgroundLayer = GradientDrawable().apply {
setColor(windowBackgroundColor)
alpha = 0
}
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
this.animator = animator
animator.duration = ANIMATION_DURATION
animator.interpolator = Interpolators.LINEAR
val launchContainerOverlay = launchContainer.overlay
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
if (DEBUG) {
Log.d(TAG, "Animation started")
}
// We animate the opening window and delegate the view expansion to [this.controller].
val delegate = this.controller
val controller = object : LaunchAnimator.Controller by delegate {
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
callback.setBlursDisabledForAppLaunch(true)
controller.onLaunchAnimationStart(isExpandingFullyAbove)
// Add the drawable to the launch container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
launchContainerOverlay.add(windowBackgroundLayer)
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
}
override fun onAnimationEnd(animation: Animator?) {
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
callback.setBlursDisabledForAppLaunch(false)
iCallback?.invoke()
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
launchContainerOverlay.remove(windowBackgroundLayer)
}
})
animator.addUpdateListener { animation ->
if (cancelled) {
return@addUpdateListener
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
}
val linearProgress = animation.animatedFraction
val progress = animationInterpolator.getInterpolation(linearProgress)
val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
val xCenter = MathUtils.lerp(startXCenter, endXCenter, xProgress)
val halfWidth = lerp(startWidth, endWidth, progress) / 2
state.top = lerp(startTop, endTop, progress).roundToInt()
state.bottom = lerp(startBottom, endBottom, progress).roundToInt()
state.left = (xCenter - halfWidth).roundToInt()
state.right = (xCenter + halfWidth).roundToInt()
state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress)
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
// The expanding view can/should be hidden once it is completely coverred by the
// windowBackgroundLayer.
state.visible =
getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
applyStateToWindow(window, state)
applyStateToWindowBackgroundLayer(windowBackgroundLayer, state, linearProgress)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
// If we started expanding the view, we make it 1 pixel smaller on all sides to
// avoid artefacts on the corners caused by anti-aliasing of the view background and
// the window background layer.
if (state.top != startTop && state.left != startLeft &&
state.bottom != startBottom && state.right != startRight) {
state.top += 1
state.left += 1
state.right -= 1
state.bottom -= 1
override fun onLaunchAnimationProgress(
state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
applyStateToWindow(window, state)
navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
}
controller.onLaunchAnimationProgress(state, progress, linearProgress)
}
animator.start()
// We draw a hole when the additional layer is fading out to reveal the opening window.
animation = launchAnimator.startAnimation(
controller, endState, windowBackgroundColor, drawHole = true)
}
private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) {
val screenBounds = window.screenSpaceBounds
val centerX = (screenBounds.left + screenBounds.right) / 2f
val centerY = (screenBounds.top + screenBounds.bottom) / 2f
@@ -632,48 +444,13 @@ class ActivityLaunchAnimator(
transactionApplier.scheduleApply(params)
}
private fun applyStateToWindowBackgroundLayer(
drawable: GradientDrawable,
state: State,
linearProgress: Float
) {
// Update position.
drawable.setBounds(state.left, state.top, state.right, state.bottom)
// Update radius.
cornerRadii[0] = state.topCornerRadius
cornerRadii[1] = state.topCornerRadius
cornerRadii[2] = state.topCornerRadius
cornerRadii[3] = state.topCornerRadius
cornerRadii[4] = state.bottomCornerRadius
cornerRadii[5] = state.bottomCornerRadius
cornerRadii[6] = state.bottomCornerRadius
cornerRadii[7] = state.bottomCornerRadius
drawable.cornerRadii = cornerRadii
// 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.
val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
if (fadeInProgress < 1) {
val alpha = CONTENT_FADE_OUT_INTERPOLATOR.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
drawable.setXfermode(null)
} else {
val fadeOutProgress = getProgress(linearProgress,
ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
drawable.setXfermode(SRC_MODE)
}
}
private fun applyStateToNavigationBar(
navigationBar: RemoteAnimationTarget,
state: State,
state: LaunchAnimator.State,
linearProgress: Float
) {
val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN,
ANIMATION_DURATION_NAV_FADE_OUT)
val fadeInProgress = LaunchAnimator.getProgress(linearProgress,
ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
if (fadeInProgress > 0) {
@@ -682,13 +459,13 @@ class ActivityLaunchAnimator(
0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
windowCrop.set(state.left, 0, state.right, state.height)
params
.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
.withMatrix(matrix)
.withWindowCrop(windowCrop)
.withVisibility(true)
.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
.withMatrix(matrix)
.withWindowCrop(windowCrop)
.withVisibility(true)
} else {
val fadeOutProgress = getProgress(linearProgress, 0,
ANIMATION_DURATION_NAV_FADE_OUT)
val fadeOutProgress = LaunchAnimator.getProgress(linearProgress, 0,
ANIMATION_DURATION_NAV_FADE_OUT)
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
}
@@ -714,7 +491,7 @@ class ActivityLaunchAnimator(
cancelled = true
removeTimeout()
context.mainExecutor.execute {
animator?.cancel()
animation?.cancel()
controller.onLaunchAnimationCancelled()
}
}
@@ -726,9 +503,5 @@ class ActivityLaunchAnimator(
e.printStackTrace()
}
}
private fun lerp(start: Int, stop: Int, amount: Float): Float {
return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount)
}
}
}

View File

@@ -1,3 +1,19 @@
/*
* 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.
*/
package com.android.systemui.animation
/**

View File

@@ -1,7 +1,24 @@
/*
* 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.
*/
package com.android.systemui.animation
import android.graphics.Canvas
import android.graphics.ColorFilter
import android.graphics.Insets
import android.graphics.Matrix
import android.graphics.PixelFormat
import android.graphics.Rect
@@ -42,6 +59,7 @@ open class GhostedViewLaunchAnimatorController(
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
get() = launchContainer.overlay
private val launchContainerLocation = IntArray(2)
/** The ghost view that is drawn and animated instead of the ghosted view. */
private var ghostView: GhostView? = null
@@ -59,8 +77,12 @@ open class GhostedViewLaunchAnimatorController(
* [backgroundView].
*/
private var backgroundDrawable: WrappedDrawable? = null
private val backgroundInsets by lazy { getBackground()?.opticalInsets ?: Insets.NONE }
private var startBackgroundAlpha: Int = 0xFF
private val ghostedViewLocation = IntArray(2)
private val ghostedViewState = LaunchAnimator.State()
/**
* Return the background of the [ghostedView]. This background will be used to draw the
* background of the background view that is expanding up to the final animation position. This
@@ -103,16 +125,24 @@ open class GhostedViewLaunchAnimatorController(
return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius
}
override fun createAnimatorState(): ActivityLaunchAnimator.State {
val location = ghostedView.locationOnScreen
return ActivityLaunchAnimator.State(
top = location[1],
bottom = location[1] + ghostedView.height,
left = location[0],
right = location[0] + ghostedView.width,
override fun createAnimatorState(): LaunchAnimator.State {
val state = LaunchAnimator.State(
topCornerRadius = getCurrentTopCornerRadius(),
bottomCornerRadius = getCurrentBottomCornerRadius()
)
fillGhostedViewState(state)
return state
}
fun fillGhostedViewState(state: LaunchAnimator.State) {
// For the animation we are interested in the area that has a non transparent background,
// so we have to take the optical insets into account.
ghostedView.getLocationOnScreen(ghostedViewLocation)
val insets = backgroundInsets
state.top = ghostedViewLocation[1] + insets.top
state.bottom = ghostedViewLocation[1] + ghostedView.height - insets.bottom
state.left = ghostedViewLocation[0] + insets.left
state.right = ghostedViewLocation[0] + ghostedView.width - insets.right
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
@@ -144,7 +174,7 @@ open class GhostedViewLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
state: ActivityLaunchAnimator.State,
state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {
@@ -162,19 +192,47 @@ open class GhostedViewLaunchAnimatorController(
return
}
val scale = min(state.widthRatio, state.heightRatio)
ghostViewMatrix.setValues(initialGhostViewMatrixValues)
ghostViewMatrix.postScale(scale, scale, state.startCenterX, state.startCenterY)
// The ghost and backgrounds views were made invisible earlier. That can for instance happen
// when animating a dialog into a view.
if (ghostView.visibility == View.INVISIBLE) {
ghostView.visibility = View.VISIBLE
backgroundView.visibility = View.VISIBLE
}
fillGhostedViewState(ghostedViewState)
val leftChange = state.left - ghostedViewState.left
val rightChange = state.right - ghostedViewState.right
val topChange = state.top - ghostedViewState.top
val bottomChange = state.bottom - ghostedViewState.bottom
val widthRatio = state.width.toFloat() / ghostedViewState.width
val heightRatio = state.height.toFloat() / ghostedViewState.height
val scale = min(widthRatio, heightRatio)
launchContainer.getLocationOnScreen(launchContainerLocation)
GhostView.calculateMatrix(ghostedView, launchContainer, ghostViewMatrix)
ghostViewMatrix.postScale(
scale, scale,
ghostedViewState.centerX - launchContainerLocation[0],
ghostedViewState.centerY - launchContainerLocation[1]
)
ghostViewMatrix.postTranslate(
(state.leftChange + state.rightChange) / 2f,
(state.topChange + state.bottomChange) / 2f
(leftChange + rightChange) / 2f,
(topChange + bottomChange) / 2f
)
ghostView.animationMatrix = ghostViewMatrix
backgroundView.top = state.top
backgroundView.bottom = state.bottom
backgroundView.left = state.left
backgroundView.right = state.right
// We need to take into account the background insets for the background position.
val insets = backgroundInsets
val topWithInsets = state.top - insets.top
val leftWithInsets = state.left - insets.left
val rightWithInsets = state.right + insets.right
val bottomWithInsets = state.bottom + insets.bottom
backgroundView.top = topWithInsets - launchContainerLocation[1]
backgroundView.bottom = bottomWithInsets - launchContainerLocation[1]
backgroundView.left = leftWithInsets - launchContainerLocation[0]
backgroundView.right = rightWithInsets - launchContainerLocation[0]
val backgroundDrawable = backgroundDrawable!!
backgroundDrawable.wrapped?.let {
@@ -207,7 +265,7 @@ open class GhostedViewLaunchAnimatorController(
* [drawable] is a [LayerDrawable], this will return the first layer that is a
* [GradientDrawable].
*/
private fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
fun findGradientDrawable(drawable: Drawable): GradientDrawable? {
if (drawable is GradientDrawable) {
return drawable
}

View File

@@ -0,0 +1,355 @@
/*
* 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.
*/
package com.android.systemui.animation
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.drawable.GradientDrawable
import android.util.Log
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import android.view.animation.PathInterpolator
import kotlin.math.roundToInt
private const val TAG = "LaunchAnimator"
/** A base class to animate a window launch (activity or dialog) from a view . */
class LaunchAnimator @JvmOverloads constructor(
context: Context,
private val isForTesting: Boolean = false
) {
companion object {
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)
/**
* Given the [linearProgress] of a launch animation, return the linear progress of the
* sub-animation starting [delay] ms after the launch animation and that lasts [duration].
*/
@JvmStatic
fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float {
return MathUtils.constrain(
(linearProgress * ANIMATION_DURATION - delay) / duration,
0.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 cornerRadii = FloatArray(8)
/**
* A controller that takes care of applying the animation to an expanding view.
*
* Note that all callbacks (onXXX methods) are all called on the main thread.
*/
interface Controller {
/**
* The container in which the view that started the animation will be animating together
* with the opening window.
*
* This will be used to:
* - Get the associated [Context].
* - Compute whether we are expanding fully above the launch container.
* - Apply surface transactions in sync with RenderThread when animating an activity
* launch.
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
* animation.
*/
var launchContainer: ViewGroup
/**
* Return the [State] of the view that will be animated. We will animate from this state to
* the final window state.
*
* Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the
* animation.
*/
fun createAnimatorState(): State
/**
* The animation started. This is typically used to initialize any additional resource
* needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding
* fully above the [launchContainer].
*/
fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {}
/** The animation made progress and the expandable view [state] should be updated. */
fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {}
/**
* The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was
* called previously. This is typically used to clean up the resources initialized when the
* animation was started.
*/
fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {}
}
/** The state of an expandable view during a [LaunchAnimator] animation. */
open class State(
/** The position of the view in screen space coordinates. */
var top: Int = 0,
var bottom: Int = 0,
var left: Int = 0,
var right: Int = 0,
var topCornerRadius: Float = 0f,
var bottomCornerRadius: Float = 0f
) {
private val startTop = top
val width: Int
get() = right - left
val height: Int
get() = bottom - top
open val topChange: Int
get() = top - startTop
val centerX: Float
get() = left + width / 2f
val centerY: Float
get() = top + height / 2f
/** Whether the expanding view should be visible or hidden. */
var visible: Boolean = true
}
interface Animation {
/** Cancel the animation. */
fun cancel()
}
/**
* 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
* should be the same background color as the opening (or closing) window. If [drawHole] is
* true, then this intermediary layer will be drawn with SRC blending mode while it fades out.
*
* TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead.
*/
fun startAnimation(
controller: Controller,
endState: State,
windowBackgroundColor: Int,
drawHole: Boolean = false
): Animation {
val state = controller.createAnimatorState()
// Start state.
val startTop = state.top
val startBottom = state.bottom
val startLeft = state.left
val startRight = state.right
val startCenterX = (startLeft + startRight) / 2f
val startWidth = startRight - startLeft
val startTopCornerRadius = state.topCornerRadius
val startBottomCornerRadius = state.bottomCornerRadius
// End state.
var endTop = endState.top
var endBottom = endState.bottom
var endLeft = endState.left
var endRight = endState.right
var endCenterX = (endLeft + endRight) / 2f
var endWidth = endRight - endLeft
val endTopCornerRadius = endState.topCornerRadius
val endBottomCornerRadius = endState.bottomCornerRadius
fun maybeUpdateEndState() {
if (endTop != endState.top || endBottom != endState.bottom ||
endLeft != endState.left || endRight != endState.right) {
endTop = endState.top
endBottom = endState.bottom
endLeft = endState.left
endRight = endState.right
endCenterX = (endLeft + endRight) / 2f
endWidth = endRight - endLeft
}
}
val launchContainer = controller.launchContainer
val isExpandingFullyAbove = isExpandingFullyAbove(launchContainer, endState)
// We add an extra layer with the same color as the dialog/app splash screen background
// color, which is usually the same color of the app background. We first fade in this layer
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
// launch container and reveal the opening window.
val windowBackgroundLayer = GradientDrawable().apply {
setColor(windowBackgroundColor)
alpha = 0
}
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = if (isForTesting) 0 else ANIMATION_DURATION
animator.interpolator = Interpolators.LINEAR
val launchContainerOverlay = launchContainer.overlay
var cancelled = false
animator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
if (DEBUG) {
Log.d(TAG, "Animation started")
}
controller.onLaunchAnimationStart(isExpandingFullyAbove)
// Add the drawable to the launch container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
launchContainerOverlay.add(windowBackgroundLayer)
}
override fun onAnimationEnd(animation: Animator?) {
if (DEBUG) {
Log.d(TAG, "Animation ended")
}
controller.onLaunchAnimationEnd(isExpandingFullyAbove)
launchContainerOverlay.remove(windowBackgroundLayer)
}
})
animator.addUpdateListener { animation ->
if (cancelled) {
// TODO(b/184121838): Cancel the animator directly instead of just skipping the
// update.
return@addUpdateListener
}
maybeUpdateEndState()
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
// reversed animation.
val linearProgress = animation.animatedFraction
val progress = animationInterpolator.getInterpolation(linearProgress)
val xProgress = animationInterpolatorX.getInterpolation(linearProgress)
val xCenter = MathUtils.lerp(startCenterX, endCenterX, xProgress)
val halfWidth = MathUtils.lerp(startWidth, endWidth, progress) / 2f
state.top = MathUtils.lerp(startTop, endTop, progress).roundToInt()
state.bottom = MathUtils.lerp(startBottom, endBottom, progress).roundToInt()
state.left = (xCenter - halfWidth).roundToInt()
state.right = (xCenter + halfWidth).roundToInt()
state.topCornerRadius =
MathUtils.lerp(startTopCornerRadius, endTopCornerRadius, progress)
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress)
// The expanding view can/should be hidden once it is completely covered by the opening
// window.
state.visible = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT) < 1
applyStateToWindowBackgroundLayer(
windowBackgroundLayer,
state,
linearProgress,
launchContainer,
drawHole
)
controller.onLaunchAnimationProgress(state, progress, linearProgress)
}
animator.start()
return object : Animation {
override fun cancel() {
cancelled = true
animator.cancel()
}
}
}
/** Return whether we are expanding fully above the [launchContainer]. */
internal fun isExpandingFullyAbove(launchContainer: View, endState: State): Boolean {
launchContainer.getLocationOnScreen(launchContainerLocation)
return endState.top <= launchContainerLocation[1] &&
endState.bottom >= launchContainerLocation[1] + launchContainer.height &&
endState.left <= launchContainerLocation[0] &&
endState.right >= launchContainerLocation[0] + launchContainer.width
}
private fun applyStateToWindowBackgroundLayer(
drawable: GradientDrawable,
state: State,
linearProgress: Float,
launchContainer: View,
drawHole: Boolean
) {
// Update position.
launchContainer.getLocationOnScreen(launchContainerLocation)
drawable.setBounds(
state.left - launchContainerLocation[0],
state.top - launchContainerLocation[1],
state.right - launchContainerLocation[0],
state.bottom - launchContainerLocation[1]
)
// Update radius.
cornerRadii[0] = state.topCornerRadius
cornerRadii[1] = state.topCornerRadius
cornerRadii[2] = state.topCornerRadius
cornerRadii[3] = state.topCornerRadius
cornerRadii[4] = state.bottomCornerRadius
cornerRadii[5] = state.bottomCornerRadius
cornerRadii[6] = state.bottomCornerRadius
cornerRadii[7] = state.bottomCornerRadius
drawable.cornerRadii = cornerRadii
// 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.
val fadeInProgress = getProgress(linearProgress, 0, ANIMATION_DURATION_FADE_OUT_CONTENT)
if (fadeInProgress < 1) {
val alpha = Interpolators.LINEAR_OUT_SLOW_IN.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
} else {
val fadeOutProgress = getProgress(
linearProgress, ANIMATION_DELAY_FADE_IN_WINDOW, ANIMATION_DURATION_FADE_IN_WINDOW)
val alpha = 1 - WINDOW_FADE_IN_INTERPOLATOR.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
if (drawHole) {
drawable.setXfermode(SRC_MODE)
}
}
}
}

View File

@@ -22,6 +22,8 @@ import android.content.Context;
import android.os.Handler;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -261,4 +263,20 @@ public interface StatusBarDependenciesModule {
@Binds
QSCarrierGroupController.SlotIndexResolver provideSlotIndexResolver(
QSCarrierGroupController.SubscriptionManagerSlotIndexResolver impl);
/**
*/
@Provides
@SysUISingleton
static LaunchAnimator provideLaunchAnimator(Context context) {
return new LaunchAnimator(context);
}
/**
*/
@Provides
@SysUISingleton
static ActivityLaunchAnimator provideActivityLaunchAnimator(LaunchAnimator launchAnimator) {
return new ActivityLaunchAnimator(launchAnimator);
}
}

View File

@@ -2,8 +2,8 @@ package com.android.systemui.statusbar.notification
import android.util.MathUtils
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.Interpolators
import com.android.systemui.animation.LaunchAnimator
import kotlin.math.min
/** Parameters for the notifications expand animations. */
@@ -15,7 +15,7 @@ class ExpandAnimationParameters(
topCornerRadius: Float = 0f,
bottomCornerRadius: Float = 0f
) : ActivityLaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
) : LaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
@VisibleForTesting
constructor() : this(
top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
@@ -55,6 +55,6 @@ class ExpandAnimationParameters(
}
fun getProgress(delay: Long, duration: Long): Float {
return ActivityLaunchAnimator.getProgress(linearProgress, delay, duration)
return LaunchAnimator.getProgress(linearProgress, delay, duration)
}
}

View File

@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification
import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
@@ -54,7 +55,7 @@ class NotificationLaunchAnimatorController(
// Do nothing. Notifications are always animated inside their rootView.
}
override fun createAnimatorState(): ActivityLaunchAnimator.State {
override fun createAnimatorState(): LaunchAnimator.State {
// If the notification panel is collapsed, the clip may be larger than the height.
val height = max(0, notification.actualHeight - notification.clipBottomAmount)
val location = notification.locationOnScreen
@@ -72,12 +73,12 @@ class NotificationLaunchAnimatorController(
notification.currentBackgroundRadiusTop
}
val params = ExpandAnimationParameters(
top = windowTop,
bottom = location[1] + height,
left = location[0],
right = location[0] + notification.width,
topCornerRadius = topCornerRadius,
bottomCornerRadius = notification.currentBackgroundRadiusBottom
top = windowTop,
bottom = location[1] + height,
left = location[0],
right = location[0] + notification.width,
topCornerRadius = topCornerRadius,
bottomCornerRadius = notification.currentBackgroundRadiusBottom
)
params.startTranslationZ = notification.translationZ
@@ -86,8 +87,8 @@ class NotificationLaunchAnimatorController(
params.startClipTopAmount = notification.clipTopAmount
if (notification.isChildInGroup) {
params.startNotificationTop += notification.notificationParent.translationY
val parentRoundedClip = Math.max(clipStartLocation
- notification.notificationParent.locationOnScreen[1], 0)
val parentRoundedClip = Math.max(
clipStartLocation - notification.notificationParent.locationOnScreen[1], 0)
params.parentStartRoundedTopClipping = parentRoundedClip
val parentClip = notification.notificationParent.clipTopAmount
@@ -157,7 +158,7 @@ class NotificationLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
state: ActivityLaunchAnimator.State,
state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {

View File

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

View File

@@ -663,7 +663,7 @@ public class StatusBar extends SystemUI implements
private final SysuiStatusBarStateController mStatusBarStateController;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private ActivityLaunchAnimator mActivityLaunchAnimator;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
@@ -782,7 +782,8 @@ public class StatusBar extends SystemUI implements
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
Optional<StartingSurface> startingSurfaceOptional,
TunerService tunerService,
DumpManager dumpManager) {
DumpManager dumpManager,
ActivityLaunchAnimator activityLaunchAnimator) {
super(context);
mNotificationsController = notificationsController;
mLightBarController = lightBarController;
@@ -886,6 +887,7 @@ public class StatusBar extends SystemUI implements
});
mActivityIntentHelper = new ActivityIntentHelper(mContext);
mActivityLaunchAnimator = activityLaunchAnimator;
// TODO(b/190746471): Find a better home for this.
DateTimeView.setReceiverHandler(timeTickHandler);
@@ -1433,7 +1435,7 @@ public class StatusBar extends SystemUI implements
private void setUpPresenter() {
// Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(mKeyguardHandler, mContext);
mActivityLaunchAnimator.setCallback(mKeyguardHandler);
mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
mNotificationShadeWindowViewController,
mStackScrollerController.getNotificationListContainer(),

View File

@@ -1,6 +1,7 @@
package com.android.systemui.statusbar.phone
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
/**
* A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
@@ -22,7 +23,7 @@ class StatusBarLaunchAnimatorController(
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
statusBar.notificationPanelViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
statusBar.collapsePanelWithDuration(ActivityLaunchAnimator.ANIMATION_DURATION.toInt())
statusBar.collapsePanelWithDuration(LaunchAnimator.ANIMATION_DURATION.toInt())
}
}
@@ -33,7 +34,7 @@ class StatusBarLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
state: ActivityLaunchAnimator.State,
state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {

View File

@@ -28,6 +28,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
@@ -227,7 +228,8 @@ public interface StatusBarPhoneModule {
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
Optional<StartingSurface> startingSurfaceOptional,
TunerService tunerService,
DumpManager dumpManager) {
DumpManager dumpManager,
ActivityLaunchAnimator activityLaunchAnimator) {
return new StatusBar(
context,
notificationsController,
@@ -321,6 +323,7 @@ public interface StatusBarPhoneModule {
unlockedScreenOffAnimationController,
startingSurfaceOptional,
tunerService,
dumpManager);
dumpManager,
activityLaunchAnimator);
}
}

View File

@@ -46,6 +46,7 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
class ActivityLaunchAnimatorTest : SysuiTestCase() {
private val launchContainer = LinearLayout(mContext)
private val launchAnimator = LaunchAnimator(mContext, isForTesting = true)
@Mock lateinit var callback: ActivityLaunchAnimator.Callback
@Spy private val controller = TestLaunchAnimatorController(launchContainer)
@Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
@@ -56,7 +57,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Before
fun setup() {
activityLaunchAnimator = ActivityLaunchAnimator(callback, mContext)
activityLaunchAnimator = ActivityLaunchAnimator(launchAnimator)
activityLaunchAnimator.callback = callback
}
private fun startIntentWithAnimation(
@@ -120,7 +122,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
@Test
fun animatesIfActivityIsAlreadyOpenAndIsOnKeyguard() {
`when`(callback.isOnKeyguard()).thenReturn(true)
val animator = ActivityLaunchAnimator(callback, context)
val animator = ActivityLaunchAnimator(launchAnimator)
animator.callback = callback
val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
var animationAdapter: RemoteAnimationAdapter? = null
@@ -208,7 +211,7 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() {
private class TestLaunchAnimatorController(
override var launchContainer: ViewGroup
) : ActivityLaunchAnimator.Controller {
override fun createAnimatorState() = ActivityLaunchAnimator.State(
override fun createAnimatorState() = LaunchAnimator.State(
top = 100,
bottom = 200,
left = 300,
@@ -232,7 +235,7 @@ private class TestLaunchAnimatorController(
}
override fun onLaunchAnimationProgress(
state: ActivityLaunchAnimator.State,
state: LaunchAnimator.State,
progress: Float,
linearProgress: Float
) {

View File

@@ -32,7 +32,7 @@ class GhostedViewLaunchAnimatorControllerTest : SysuiTestCase() {
fun animatingOrphanViewDoesNotCrash() {
val ghostedView = LinearLayout(mContext)
val controller = GhostedViewLaunchAnimatorController(ghostedView)
val state = ActivityLaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
val state = LaunchAnimator.State(top = 0, bottom = 0, left = 0, right = 0)
controller.onIntentStarted(willAnimate = true)
controller.onLaunchAnimationStart(isExpandingFullyAbove = true)

View File

@@ -77,6 +77,7 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollectorFake;
@@ -268,6 +269,7 @@ public class StatusBarTest extends SysuiTestCase {
@Mock private StartingSurface mStartingSurface;
@Mock private OperatorNameViewController mOperatorNameViewController;
@Mock private OperatorNameViewController.Factory mOperatorNameViewControllerFactory;
@Mock private ActivityLaunchAnimator mActivityLaunchAnimator;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock);
@@ -442,7 +444,8 @@ public class StatusBarTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController,
Optional.of(mStartingSurface),
mTunerService,
mock(DumpManager.class));
mock(DumpManager.class),
mActivityLaunchAnimator);
when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
any(ViewGroup.class), any(KeyguardBypassController.class)))