Extract ActivityLaunchAnimator into a reusable class.
This CL removes all logic specific to notifications in
ActivityLaunchAnimator, so that it can be reused for other launch
animations.
I would suggest to look at ag/14057092 in parallel to make more sense of
the abstractions added in this CL.
For the sake of not making this CL even bigger than it already is, a few
things still need to be done in follow-up CLs:
- Show the status bar icons at the right time, instead of at the end of
the animation, when using the StatusBarLaunchAnimatorController.
- Move the animation/ package outside of the SystemUIPluginLib library
and instead have it in its own reusable library.
- Replace the animation durations and interpolator to the latest
designs.
- Improve split screen by retrieving the final window bounds and
prevent the clipping of the window during the animation.
- Handle animations in the lock screen.
For review, I would recommend to review in order:
1. ActivityLaunchAnimator.kt
2. StatusBarLaunchAnimatorController.kt
3. NotificationLaunchAnimatorController.kt
4. GhostedViewLaunchAnimatorController.kt
5. Everything else.
Bug: 184121838
Bug: 181654098
Test: Tap a notification when the shade is open and unlocked.
Change-Id: If4c3c64fcd153bb8e89111f56332013ca6dff156
diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp
index d6204db..b3aba22 100644
--- a/packages/SystemUI/plugin/Android.bp
+++ b/packages/SystemUI/plugin/Android.bp
@@ -27,6 +27,7 @@
srcs: [
"src/**/*.java",
+ "src/**/*.kt",
"bcsmartspace/src/**/*.java",
],
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 25a3fa2..e003b2e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.view.View;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.annotations.ProvidesInterface;
/**
@@ -44,7 +45,15 @@
* specifies an associated view that should be used for the activity launch animation.
*/
void startPendingIntentDismissingKeyguard(PendingIntent intent,
- Runnable intentSentUiThreadCallback, View associatedView);
+ Runnable intentSentUiThreadCallback, @Nullable View associatedView);
+
+ /**
+ * Similar to {@link #startPendingIntentDismissingKeyguard(PendingIntent, Runnable)}, but also
+ * specifies an animation controller that should be used for the activity launch animation.
+ */
+ void startPendingIntentDismissingKeyguard(PendingIntent intent,
+ Runnable intentSentUiThreadCallback,
+ @Nullable ActivityLaunchAnimator.Controller animationController);
/**
* The intent flag can be specified in startActivity().
@@ -55,6 +64,14 @@
void startActivity(Intent intent, boolean dismissShade, Callback callback);
void postStartActivityDismissingKeyguard(Intent intent, int delay);
void postStartActivityDismissingKeyguard(PendingIntent intent);
+
+ /**
+ * Similar to {@link #postStartActivityDismissingKeyguard(PendingIntent)}, but also specifies an
+ * animation controller that should be used for the activity launch animation.
+ */
+ void postStartActivityDismissingKeyguard(PendingIntent intent,
+ @Nullable ActivityLaunchAnimator.Controller animationController);
+
void postQSRunnableDismissingKeyguard(Runnable runnable);
void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel,
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt
new file mode 100644
index 0000000..e8bdb67
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt
@@ -0,0 +1,479 @@
+package com.android.systemui.plugins.animation
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.app.ActivityManager
+import android.app.PendingIntent
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.RemoteException
+import android.util.MathUtils
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.IRemoteAnimationRunner
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import android.view.WindowManager
+import android.view.animation.LinearInterpolator
+import android.view.animation.PathInterpolator
+import com.android.internal.annotations.VisibleForTesting
+import com.android.internal.policy.ScreenDecorationsUtils
+import kotlin.math.roundToInt
+
+/**
+ * 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 {
+ companion object {
+ const val ANIMATION_DURATION = 400L
+ const val ANIMATION_DURATION_FADE_OUT_CONTENT = 67L
+ const val ANIMATION_DURATION_FADE_IN_WINDOW = 200L
+ 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
+ private const val LAUNCH_TIMEOUT = 500L
+
+ // TODO(b/184121838): Use android.R.interpolator.fast_out_extra_slow_in instead.
+ // TODO(b/184121838): Move com.android.systemui.Interpolators in an animation library we can
+ // reuse here.
+ private val ANIMATION_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f)
+ private val LINEAR_INTERPOLATOR = LinearInterpolator()
+ private val ALPHA_IN_INTERPOLATOR = PathInterpolator(0.4f, 0f, 1f, 1f)
+ private val ALPHA_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.8f, 1f)
+ private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f)
+ private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f)
+
+ /**
+ * 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
+ )
+ }
+ }
+
+ /**
+ * Start an intent and animate the opening window. The intent will be started by running
+ * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch
+ * result. [controller] is responsible from animating the view from which the intent was started
+ * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window
+ * opening.
+ *
+ * If [controller] is null, then the intent will be started and no animation will run.
+ *
+ * This method will throw any exception thrown by [intentStarter].
+ */
+ inline fun startIntentWithAnimation(
+ controller: Controller?,
+ intentStarter: (RemoteAnimationAdapter?) -> Int
+ ) {
+ if (controller == null) {
+ intentStarter(null)
+ return
+ }
+
+ val runner = Runner(controller)
+ val animationAdapter = RemoteAnimationAdapter(
+ runner,
+ ANIMATION_DURATION,
+ ANIMATION_DURATION - 150 /* statusBarTransitionDelay */
+ )
+ val launchResult = intentStarter(animationAdapter)
+ val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT ||
+ launchResult == ActivityManager.START_SUCCESS
+ runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) }
+
+ // If we expect an animation, post a timeout to cancel it in case the remote animation is
+ // never started.
+ if (willAnimate) {
+ runner.postTimeout()
+ }
+ }
+
+ /**
+ * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a
+ * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful
+ * for Java caller starting a [PendingIntent].
+ */
+ @Throws(PendingIntent.CanceledException::class)
+ fun startPendingIntentWithAnimation(
+ controller: Controller?,
+ intentStarter: PendingIntentStarter
+ ) {
+ startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) }
+ }
+
+ interface PendingIntentStarter {
+ /**
+ * Start a pending intent using the provided [animationAdapter] and return the launch
+ * result.
+ */
+ @Throws(PendingIntent.CanceledException::class)
+ fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int
+ }
+
+ /**
+ * 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 {
+ companion object {
+ /**
+ * Return a [Controller] that will animate and expand [view] into the opening window.
+ *
+ * Important: The view must be attached to the window when calling this function and
+ * during the animation.
+ */
+ @JvmStatic
+ fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view)
+ }
+
+ /**
+ * Return the root [View] that contains the view that started the intent and will be
+ * animating together with the window.
+ *
+ * This view 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.
+ */
+ fun getRootView(): View
+
+ /**
+ * 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 remotely. Note that [onLaunchAnimationEnd] will still be
+ * called after this if the animation was already started, i.e. if [onLaunchAnimationStart]
+ * was called before the cancellation.
+ */
+ fun onLaunchAnimationCancelled() {}
+
+ /**
+ * The remote animation was not started within the expected time. It timed out and will
+ * never [start][onLaunchAnimationStart].
+ */
+ fun onLaunchAnimationTimedOut() {}
+
+ /**
+ * The animation was aborted because the opening window was not found. It will never
+ * [start][onLaunchAnimationStart].
+ */
+ fun onLaunchAnimationAborted() {}
+ }
+
+ /** 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,
+
+ var contentAlpha: Float = 1f,
+ var backgroundAlpha: Float = 1f
+ ) {
+ private val startTop = top
+ private val startLeft = left
+ private val startRight = right
+
+ val width: Int
+ get() = right - left
+
+ val height: Int
+ get() = bottom - top
+
+ open val topChange: Int
+ get() = top - startTop
+
+ val leftChange: Int
+ get() = left - startLeft
+
+ val rightChange: Int
+ get() = right - startRight
+ }
+
+ @VisibleForTesting
+ class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() {
+ private val rootView = controller.getRootView()
+ @PublishedApi internal val context = rootView.context
+ private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView)
+ private var animator: ValueAnimator? = null
+
+ private var windowCrop = Rect()
+ private var timedOut = false
+ private var cancelled = false
+
+ // A timeout to cancel the remote animation if it is not started within X milliseconds after
+ // the intent was started.
+ //
+ // Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise
+ // it will be automatically converted when posted and we wouldn't be able to remove it after
+ // posting it.
+ private var onTimeout = Runnable { onAnimationTimedOut() }
+
+ @PublishedApi
+ internal fun postTimeout() {
+ rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT)
+ }
+
+ private fun removeTimeout() {
+ rootView.removeCallbacks(onTimeout)
+ }
+
+ override fun onAnimationStart(
+ @WindowManager.TransitionOldType transit: Int,
+ remoteAnimationTargets: Array<out RemoteAnimationTarget>,
+ remoteAnimationWallpaperTargets: Array<out RemoteAnimationTarget>,
+ remoteAnimationNonAppTargets: Array<out RemoteAnimationTarget>,
+ iRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback
+ ) {
+ removeTimeout()
+
+ // The animation was started too late and we already notified the controller that it
+ // timed out.
+ if (timedOut) {
+ invokeCallback(iRemoteAnimationFinishedCallback)
+ return
+ }
+
+ // This should not happen, but let's make sure we don't start the animation if it was
+ // cancelled before and we already notified the controller.
+ if (cancelled) {
+ return
+ }
+
+ context.mainExecutor.execute {
+ startAnimation(remoteAnimationTargets, iRemoteAnimationFinishedCallback)
+ }
+ }
+
+ private fun startAnimation(
+ remoteAnimationTargets: Array<out RemoteAnimationTarget>,
+ iCallback: IRemoteAnimationFinishedCallback
+ ) {
+ val window = remoteAnimationTargets.firstOrNull {
+ it.mode == RemoteAnimationTarget.MODE_OPENING
+ }
+
+ if (window == null) {
+ removeTimeout()
+ invokeCallback(iCallback)
+ controller.onLaunchAnimationAborted()
+ return
+ }
+
+ val navigationBar = remoteAnimationTargets.firstOrNull {
+ 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 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
+
+ // TODO(b/184121838): Ensure that we are launching on the same screen.
+ val rootViewLocation = rootView.locationOnScreen
+ val isExpandingFullyAbove = endTop <= rootViewLocation[1] &&
+ endBottom >= rootViewLocation[1] + rootView.height &&
+ endLeft <= rootViewLocation[0] &&
+ endRight >= rootViewLocation[0] + rootView.width
+
+ // TODO(b/184121838): We should somehow get the top and bottom radius of the window.
+ val endRadius = if (isExpandingFullyAbove) {
+ // Most of the time, expanding fully above the root view means expanding in full
+ // screen.
+ ScreenDecorationsUtils.getWindowCornerRadius(context.resources)
+ } else {
+ // This usually means we are in split screen mode, so 2 out of 4 corners will have
+ // a radius of 0.
+ 0f
+ }
+
+ // Update state.
+ val animator = ValueAnimator.ofFloat(0f, 1f)
+ this.animator = animator
+ animator.duration = ANIMATION_DURATION
+ animator.interpolator = LINEAR_INTERPOLATOR
+
+ animator.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+ controller.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
+
+ override fun onAnimationEnd(animation: Animator?) {
+ invokeCallback(iCallback)
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+ }
+ })
+
+ animator.addUpdateListener { animation ->
+ if (cancelled) {
+ return@addUpdateListener
+ }
+
+ val linearProgress = animation.animatedFraction
+ val progress = ANIMATION_INTERPOLATOR.getInterpolation(linearProgress)
+
+ state.top = lerp(startTop, endTop, progress).roundToInt()
+ state.bottom = lerp(startBottom, endBottom, progress).roundToInt()
+ state.left = lerp(startLeft, endLeft, progress).roundToInt()
+ state.right = lerp(startRight, endRight, progress).roundToInt()
+
+ state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress)
+ state.bottomCornerRadius =
+ MathUtils.lerp(startBottomCornerRadius, endRadius, progress)
+
+ val contentAlphaProgress = getProgress(linearProgress, 0,
+ ANIMATION_DURATION_FADE_OUT_CONTENT)
+ state.contentAlpha =
+ 1 - ALPHA_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress)
+
+ val backgroundAlphaProgress = getProgress(linearProgress,
+ ANIMATION_DURATION_FADE_OUT_CONTENT, ANIMATION_DURATION_FADE_IN_WINDOW)
+ state.backgroundAlpha =
+ 1 - ALPHA_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress)
+
+ applyStateToWindow(window, state)
+ navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
+ controller.onLaunchAnimationProgress(state, progress, linearProgress)
+ }
+
+ animator.start()
+ }
+
+ private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) {
+ val m = Matrix()
+ m.postTranslate(0f, (state.top - window.sourceContainerBounds.top).toFloat())
+ windowCrop.set(state.left, 0, state.right, state.height)
+
+ val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius)
+ val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
+ .withAlpha(1f)
+ .withMatrix(m)
+ .withWindowCrop(windowCrop)
+ .withLayer(window.prefixOrderIndex)
+ .withCornerRadius(cornerRadius)
+ .withVisibility(true)
+ .build()
+
+ transactionApplier.scheduleApply(params)
+ }
+
+ private fun applyStateToNavigationBar(
+ navigationBar: RemoteAnimationTarget,
+ state: State,
+ linearProgress: Float
+ ) {
+ val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_DURATION_NAV_FADE_OUT)
+
+ val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
+ if (fadeInProgress > 0) {
+ val m = Matrix()
+ m.postTranslate(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(m)
+ .withWindowCrop(windowCrop)
+ .withVisibility(true)
+ } else {
+ val fadeOutProgress = getProgress(linearProgress, 0,
+ ANIMATION_DURATION_NAV_FADE_OUT)
+ params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
+ }
+
+ transactionApplier.scheduleApply(params.build())
+ }
+
+ private fun onAnimationTimedOut() {
+ if (cancelled) {
+ return
+ }
+
+ timedOut = true
+ controller.onLaunchAnimationTimedOut()
+ }
+
+ override fun onAnimationCancelled() {
+ if (timedOut) {
+ return
+ }
+
+ cancelled = true
+ removeTimeout()
+ context.mainExecutor.execute {
+ animator?.cancel()
+ controller.onLaunchAnimationCancelled()
+ }
+ }
+
+ private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) {
+ try {
+ iCallback.onAnimationFinished()
+ } catch (e: RemoteException) {
+ e.printStackTrace()
+ }
+ }
+
+ private fun lerp(start: Int, stop: Int, amount: Float): Float {
+ return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount)
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt
new file mode 100644
index 0000000..a237224
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt
@@ -0,0 +1,196 @@
+package com.android.systemui.plugins.animation
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.view.GhostView
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+
+/**
+ * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView]
+ * of [ghostedView] as well as an expandable background view, which are drawn and animated instead
+ * of the ghosted view.
+ *
+ * Important: [ghostedView] must be attached to the window when calling this function and during the
+ * animation.
+ *
+ * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
+ * whenever possible instead.
+ */
+open class GhostedViewLaunchAnimatorController(
+ /** The view that will be ghosted and from which the background will be extracted. */
+ private val ghostedView: View
+) : ActivityLaunchAnimator.Controller {
+ /** The root view to which we will add the ghost view and expanding background. */
+ private val rootView = ghostedView.rootView as ViewGroup
+ private val rootViewOverlay = rootView.overlay
+
+ /** The ghost view that is drawn and animated instead of the ghosted view. */
+ private var ghostView: View? = null
+
+ /**
+ * The expanding background view that will be added to [rootView] (below [ghostView]) and
+ * animate.
+ */
+ private var backgroundView: FrameLayout? = null
+
+ /**
+ * The drawable wrapping the [ghostedView] background and used as background for
+ * [backgroundView].
+ */
+ private var backgroundDrawable: WrappedDrawable? = null
+ private var startBackgroundAlpha: Int = 0xFF
+
+ /**
+ * 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
+ * is called at the start of the animation.
+ *
+ * Note that during the animation, the alpha value value of this background will be set to 0,
+ * then set back to its initial value at the end of the animation.
+ */
+ protected open fun getBackground(): Drawable? = ghostedView.background
+
+ /**
+ * Set the corner radius of [background]. The background is the one that was returned by
+ * [getBackground].
+ */
+ protected open fun setBackgroundCornerRadius(
+ background: Drawable,
+ topCornerRadius: Float,
+ bottomCornerRadius: Float
+ ) {
+ // TODO(b/184121838): Add default support for GradientDrawable and LayerDrawable to make
+ // this work out of the box for common rounded backgrounds.
+ }
+
+ /** Return the current top corner radius of the background. */
+ protected open fun getCurrentTopCornerRadius(): Float = 0f
+
+ /** Return the current bottom corner radius of the background. */
+ protected open fun getCurrentBottomCornerRadius(): Float = 0f
+
+ override fun getRootView(): View {
+ return rootView
+ }
+
+ 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,
+ topCornerRadius = getCurrentTopCornerRadius(),
+ bottomCornerRadius = getCurrentBottomCornerRadius()
+ )
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ backgroundView = FrameLayout(rootView.context).apply {
+ forceHasOverlappingRendering(true)
+ }
+ rootViewOverlay.add(backgroundView)
+
+ // We wrap the ghosted view background and use it to draw the expandable background. Its
+ // alpha will be set to 0 as soon as we start drawing the expanding background.
+ val drawable = getBackground()
+ startBackgroundAlpha = drawable?.alpha ?: 0xFF
+ backgroundDrawable = WrappedDrawable(drawable)
+ backgroundView?.background = backgroundDrawable
+
+ // Create a ghost of the view that will be moving and fading out. This allows to fade out
+ // the content before fading out the background.
+ ghostView = GhostView.addGhost(ghostedView, rootView).apply {
+ setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ }
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: ActivityLaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ val ghostView = this.ghostView!!
+ ghostView.translationX = (state.leftChange + state.rightChange) / 2.toFloat()
+ ghostView.translationY = state.topChange.toFloat()
+ ghostView.alpha = state.contentAlpha
+
+ val backgroundView = this.backgroundView!!
+ backgroundView.top = state.top
+ backgroundView.bottom = state.bottom
+ backgroundView.left = state.left
+ backgroundView.right = state.right
+
+ val backgroundDrawable = backgroundDrawable!!
+ backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt()
+ backgroundDrawable.wrapped?.let {
+ setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius)
+ }
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
+
+ GhostView.removeGhost(ghostedView)
+ rootViewOverlay.remove(backgroundView)
+ ghostedView.invalidate()
+ }
+
+ private class WrappedDrawable(val wrapped: Drawable?) : Drawable() {
+ companion object {
+ private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
+ }
+
+ private var currentAlpha = 0xFF
+ private var previousBounds = Rect()
+
+ override fun draw(canvas: Canvas) {
+ val wrapped = this.wrapped ?: return
+
+ wrapped.copyBounds(previousBounds)
+
+ wrapped.alpha = currentAlpha
+ wrapped.bounds = bounds
+ wrapped.setXfermode(SRC_MODE)
+
+ wrapped.draw(canvas)
+
+ // The background view (and therefore this drawable) is drawn before the ghost view, so
+ // the ghosted view background alpha should always be 0 when it is drawn above the
+ // background.
+ wrapped.alpha = 0
+ wrapped.bounds = previousBounds
+ wrapped.setXfermode(null)
+ }
+
+ override fun setAlpha(alpha: Int) {
+ if (alpha != currentAlpha) {
+ currentAlpha = alpha
+ invalidateSelf()
+ }
+ }
+
+ override fun getAlpha() = currentAlpha
+
+ override fun getOpacity(): Int {
+ val wrapped = this.wrapped ?: return PixelFormat.TRANSPARENT
+
+ val previousAlpha = wrapped.alpha
+ wrapped.alpha = currentAlpha
+ val opacity = wrapped.opacity
+ wrapped.alpha = previousAlpha
+ return opacity
+ }
+
+ override fun setColorFilter(filter: ColorFilter?) {
+ wrapped?.colorFilter = filter
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
index 3d6d381..ecbb70e 100644
--- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java
@@ -20,6 +20,7 @@
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.statusbar.phone.StatusBar;
import java.util.Optional;
@@ -51,18 +52,27 @@
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
- Runnable intentSentCallback) {
+ Runnable intentSentUiThreadCallback) {
mActualStarter.ifPresent(
starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
- intentSentCallback));
+ intentSentUiThreadCallback));
}
@Override
public void startPendingIntentDismissingKeyguard(PendingIntent intent,
- Runnable intentSentCallback, View associatedView) {
+ Runnable intentSentUiThreadCallback, View associatedView) {
mActualStarter.ifPresent(
starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
- intentSentCallback, associatedView));
+ intentSentUiThreadCallback, associatedView));
+ }
+
+ @Override
+ public void startPendingIntentDismissingKeyguard(PendingIntent intent,
+ Runnable intentSentUiThreadCallback,
+ ActivityLaunchAnimator.Controller animationController) {
+ mActualStarter.ifPresent(
+ starter -> starter.get().startPendingIntentDismissingKeyguard(intent,
+ intentSentUiThreadCallback, animationController));
}
@Override
@@ -103,6 +113,13 @@
}
@Override
+ public void postStartActivityDismissingKeyguard(PendingIntent intent,
+ ActivityLaunchAnimator.Controller animationController) {
+ mActualStarter.ifPresent(starter ->
+ starter.get().postStartActivityDismissingKeyguard(intent, animationController));
+ }
+
+ @Override
public void postQSRunnableDismissingKeyguard(Runnable runnable) {
mActualStarter.ifPresent(
starter -> starter.get().postQSRunnableDismissingKeyguard(runnable));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
index 8e6398f..9525975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java
@@ -105,9 +105,7 @@
}
} else if (view.getLayerType() == View.LAYER_TYPE_HARDWARE
&& view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
- if (view.getTag(R.id.cross_fade_layer_type_changed_tag) != null) {
- view.setLayerType(View.LAYER_TYPE_NONE, null);
- }
+ view.setLayerType(View.LAYER_TYPE_NONE, null);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index e27c1a2..a0a4e31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -35,7 +35,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
@@ -111,7 +111,7 @@
* When launching an app from the shade, the animations progress should affect how blurry the
* shade is, overriding the expansion amount.
*/
- var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
+ var notificationLaunchAnimationParams: ExpandAnimationParameters? = null
set(value) {
field = value
if (value != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
index 24515f7..737167e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java
@@ -90,9 +90,14 @@
/** Sets the state of whether the user activities are forced or not. */
default void setForceUserActivity(boolean forceUserActivity) {}
- /** Sets the state of whether the user activities are forced or not. */
+ /** Sets the state of whether an activity is launching or not. */
default void setLaunchingActivity(boolean launching) {}
+ /** Get whether an activity is launching or not. */
+ default boolean isLaunchingActivity() {
+ return false;
+ }
+
/** Sets the state of whether the scrim is visible or not. */
default void setScrimsVisibility(int scrimsVisibility) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
deleted file mode 100644
index 23d13d3..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ /dev/null
@@ -1,490 +0,0 @@
-/*
- * Copyright (C) 2018 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.statusbar.notification;
-
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-
-import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.MathUtils;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SyncRtSurfaceTransactionApplier;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-
-import com.android.internal.jank.InteractionJankMonitor;
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.systemui.Interpolators;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
-
-import java.util.concurrent.Executor;
-
-/**
- * A class that allows activities to be launched in a seamless way where the notification
- * transforms nicely into the starting window.
- */
-public class ActivityLaunchAnimator {
-
- private static final int ANIMATION_DURATION = 400;
- public static final long ANIMATION_DURATION_FADE_CONTENT = 67;
- public static final long ANIMATION_DURATION_FADE_APP = 200;
- public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION -
- CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
- - 16;
- private static final int ANIMATION_DURATION_NAV_FADE_IN = 266;
- private static final int ANIMATION_DURATION_NAV_FADE_OUT = 133;
- private static final long ANIMATION_DELAY_NAV_FADE_IN =
- ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN;
- private static final Interpolator NAV_FADE_IN_INTERPOLATOR =
- new PathInterpolator(0f, 0f, 0f, 1f);
- private static final Interpolator NAV_FADE_OUT_INTERPOLATOR =
- new PathInterpolator(0.2f, 0f, 1f, 1f);
- private static final long LAUNCH_TIMEOUT = 500;
- private final NotificationPanelViewController mNotificationPanel;
- private final NotificationListContainer mNotificationContainer;
- private final float mWindowCornerRadius;
- private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
- private final NotificationShadeDepthController mDepthController;
- private final Executor mMainExecutor;
- private Callback mCallback;
- private final Runnable mTimeoutRunnable = () -> {
- setAnimationPending(false);
- mCallback.onExpandAnimationTimedOut();
- };
- private boolean mAnimationPending;
- private boolean mAnimationRunning;
- private boolean mIsLaunchForActivity;
-
- public ActivityLaunchAnimator(
- NotificationShadeWindowViewController notificationShadeWindowViewController,
- Callback callback,
- NotificationPanelViewController notificationPanel,
- NotificationShadeDepthController depthController,
- NotificationListContainer container,
- Executor mainExecutor) {
- mNotificationPanel = notificationPanel;
- mNotificationContainer = container;
- mDepthController = depthController;
- mNotificationShadeWindowViewController = notificationShadeWindowViewController;
- mCallback = callback;
- mMainExecutor = mainExecutor;
- mWindowCornerRadius = ScreenDecorationsUtils
- .getWindowCornerRadius(mNotificationShadeWindowViewController.getView()
- .getResources());
- }
-
- public RemoteAnimationAdapter getLaunchAnimation(
- View sourceView, boolean occluded) {
- if (!(sourceView instanceof ExpandableNotificationRow)
- || !mCallback.areLaunchAnimationsEnabled() || occluded) {
- return null;
- }
- AnimationRunner animationRunner = new AnimationRunner(
- (ExpandableNotificationRow) sourceView);
- return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION,
- ANIMATION_DURATION - 150 /* statusBarTransitionDelay */);
- }
-
- public boolean isAnimationPending() {
- return mAnimationPending;
- }
-
- /**
- * Set the launch result the intent requested
- *
- * @param launchResult the launch result
- * @param wasIntentActivity was this launch for an activity
- */
- public void setLaunchResult(int launchResult, boolean wasIntentActivity) {
- mIsLaunchForActivity = wasIntentActivity;
- setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT
- || launchResult == ActivityManager.START_SUCCESS)
- && mCallback.areLaunchAnimationsEnabled());
- }
-
- public boolean isLaunchForActivity() {
- return mIsLaunchForActivity;
- }
-
- private void setAnimationPending(boolean pending) {
- mAnimationPending = pending;
- mNotificationShadeWindowViewController.setExpandAnimationPending(pending);
- if (pending) {
- mNotificationShadeWindowViewController.getView().postDelayed(mTimeoutRunnable,
- LAUNCH_TIMEOUT);
- } else {
- mNotificationShadeWindowViewController.getView().removeCallbacks(mTimeoutRunnable);
- }
- }
-
- public boolean isAnimationRunning() {
- return mAnimationRunning;
- }
-
- class AnimationRunner extends IRemoteAnimationRunner.Stub {
-
- private final ExpandableNotificationRow mSourceNotification;
- private final ExpandAnimationParameters mParams;
- private final Rect mWindowCrop = new Rect();
- private boolean mIsFullScreenLaunch = true;
- private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier;
-
- private final float mNotificationStartTopCornerRadius;
- private final float mNotificationStartBottomCornerRadius;
-
- AnimationRunner(ExpandableNotificationRow sourceNotification) {
- mSourceNotification = sourceNotification;
- mParams = new ExpandAnimationParameters();
- mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification);
- mNotificationStartTopCornerRadius = mSourceNotification.getCurrentBackgroundRadiusTop();
- mNotificationStartBottomCornerRadius =
- mSourceNotification.getCurrentBackgroundRadiusBottom();
- }
-
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] remoteAnimationTargets,
- RemoteAnimationTarget[] remoteAnimationWallpaperTargets,
- RemoteAnimationTarget[] remoteAnimationNonAppTargets,
- IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
- throws RemoteException {
- mMainExecutor.execute(() -> {
- RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget(
- remoteAnimationTargets);
- if (primary == null) {
- setAnimationPending(false);
- invokeCallback(iRemoteAnimationFinishedCallback);
- mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- return;
- }
-
- setExpandAnimationRunning(true);
- mIsFullScreenLaunch = primary.position.y == 0
- && primary.sourceContainerBounds.height()
- >= mNotificationPanel.getHeight();
- if (!mIsFullScreenLaunch) {
- mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
- }
- ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
- mParams.startPosition = mSourceNotification.getLocationOnScreen();
- mParams.startTranslationZ = mSourceNotification.getTranslationZ();
- mParams.startClipTopAmount = mSourceNotification.getClipTopAmount();
- if (mSourceNotification.isChildInGroup()) {
- int parentClip = mSourceNotification
- .getNotificationParent().getClipTopAmount();
- mParams.parentStartClipTopAmount = parentClip;
- // We need to calculate how much the child is clipped by the parent
- // because children always have 0 clipTopAmount
- if (parentClip != 0) {
- float childClip = parentClip
- - mSourceNotification.getTranslationY();
- if (childClip > 0.0f) {
- mParams.startClipTopAmount = (int) Math.ceil(childClip);
- }
- }
- }
- int targetWidth = primary.sourceContainerBounds.width();
- // If the notification panel is collapsed, the clip may be larger than the height.
- int notificationHeight = Math.max(mSourceNotification.getActualHeight()
- - mSourceNotification.getClipBottomAmount(), 0);
- int notificationWidth = mSourceNotification.getWidth();
- final RemoteAnimationTarget navigationBarTarget =
- getNavBarRemoteAnimationTarget(remoteAnimationNonAppTargets);
- anim.setDuration(ANIMATION_DURATION);
- anim.setInterpolator(Interpolators.LINEAR);
- anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mParams.linearProgress = animation.getAnimatedFraction();
- float progress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
- mParams.linearProgress);
- int newWidth = (int) MathUtils.lerp(notificationWidth,
- targetWidth, progress);
- mParams.left = (int) ((targetWidth - newWidth) / 2.0f);
- mParams.right = mParams.left + newWidth;
- mParams.top = (int) MathUtils.lerp(mParams.startPosition[1],
- primary.position.y, progress);
- mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1]
- + notificationHeight,
- primary.position.y + primary.sourceContainerBounds.bottom,
- progress);
- mParams.topCornerRadius = MathUtils.lerp(mNotificationStartTopCornerRadius,
- mWindowCornerRadius, progress);
- mParams.bottomCornerRadius = MathUtils.lerp(
- mNotificationStartBottomCornerRadius,
- mWindowCornerRadius, progress);
- applyParamsToWindow(primary);
- applyParamsToNotification(mParams);
- applyParamsToNotificationShade(mParams);
- applyNavigationBarParamsToWindow(navigationBarTarget);
- }
- });
- anim.addListener(new AnimatorListenerAdapter() {
- private boolean mWasCancelled;
-
- @Override
- public void onAnimationStart(Animator animation) {
- InteractionJankMonitor.getInstance().begin(mSourceNotification,
- CUJ_NOTIFICATION_APP_START);
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mWasCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- setExpandAnimationRunning(false);
- invokeCallback(iRemoteAnimationFinishedCallback);
- if (!mWasCancelled) {
- InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_APP_START);
- } else {
- InteractionJankMonitor.getInstance().cancel(CUJ_NOTIFICATION_APP_START);
- }
- }
- });
- anim.start();
- setAnimationPending(false);
- });
- }
-
- private void invokeCallback(IRemoteAnimationFinishedCallback callback) {
- try {
- callback.onAnimationFinished();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
-
- private RemoteAnimationTarget getPrimaryRemoteAnimationTarget(
- RemoteAnimationTarget[] remoteAnimationTargets) {
- RemoteAnimationTarget primary = null;
- for (RemoteAnimationTarget app : remoteAnimationTargets) {
- if (app.mode == RemoteAnimationTarget.MODE_OPENING) {
- primary = app;
- break;
- }
- }
- return primary;
- }
-
- private RemoteAnimationTarget getNavBarRemoteAnimationTarget(
- RemoteAnimationTarget[] remoteAnimationTargets) {
- RemoteAnimationTarget navBar = null;
- for (RemoteAnimationTarget target : remoteAnimationTargets) {
- if (target.windowType == TYPE_NAVIGATION_BAR) {
- navBar = target;
- break;
- }
- }
- return navBar;
- }
-
- private void setExpandAnimationRunning(boolean running) {
- mNotificationPanel.setLaunchingNotification(running);
- mSourceNotification.setExpandAnimationRunning(running);
- mNotificationShadeWindowViewController.setExpandAnimationRunning(running);
- mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
- mAnimationRunning = running;
- if (!running) {
- mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
- applyParamsToNotification(null);
- applyParamsToNotificationShade(null);
- }
-
- }
-
- private void applyParamsToNotificationShade(ExpandAnimationParameters params) {
- mNotificationContainer.applyExpandAnimationParams(params);
- mNotificationPanel.applyExpandAnimationParams(params);
- mDepthController.setNotificationLaunchAnimationParams(params);
- }
-
- private void applyParamsToNotification(ExpandAnimationParameters params) {
- mSourceNotification.applyExpandAnimationParams(params);
- }
-
- private void applyParamsToWindow(RemoteAnimationTarget app) {
- Matrix m = new Matrix();
- m.postTranslate(0, (float) (mParams.top - app.position.y));
- mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
- float cornerRadius = Math.min(mParams.topCornerRadius, mParams.bottomCornerRadius);
- SurfaceParams params = new SurfaceParams.Builder(app.leash)
- .withAlpha(1f)
- .withMatrix(m)
- .withWindowCrop(mWindowCrop)
- .withLayer(app.prefixOrderIndex)
- .withCornerRadius(cornerRadius)
- .withVisibility(true)
- .build();
- mSyncRtTransactionApplier.scheduleApply(params);
- }
-
- private void applyNavigationBarParamsToWindow(RemoteAnimationTarget navBarTarget) {
- if (navBarTarget == null) {
- return;
- }
-
- // calculate navigation bar fade-out progress
- final float fadeOutProgress = mParams.getProgress(0,
- ANIMATION_DURATION_NAV_FADE_OUT);
-
- // calculate navigation bar fade-in progress
- final float fadeInProgress = mParams.getProgress(ANIMATION_DELAY_NAV_FADE_IN,
- ANIMATION_DURATION_NAV_FADE_OUT);
-
- final SurfaceParams.Builder builder = new SurfaceParams.Builder(navBarTarget.leash);
- if (fadeInProgress > 0) {
- Matrix m = new Matrix();
- m.postTranslate(0, (float) (mParams.top - navBarTarget.position.y));
- mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
- builder.withMatrix(m)
- .withWindowCrop(mWindowCrop)
- .withVisibility(true);
- builder.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress));
- } else {
- builder.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress));
- }
- mSyncRtTransactionApplier.scheduleApply(builder.build());
- }
-
- @Override
- public void onAnimationCancelled() throws RemoteException {
- mMainExecutor.execute(() -> {
- setAnimationPending(false);
- mCallback.onLaunchAnimationCancelled();
- });
- }
- };
-
- public static class ExpandAnimationParameters {
- public float linearProgress;
- int[] startPosition;
- float startTranslationZ;
- int left;
- int top;
- int right;
- int bottom;
- int startClipTopAmount;
- int parentStartClipTopAmount;
- float topCornerRadius;
- float bottomCornerRadius;
-
- public ExpandAnimationParameters() {
- }
-
- public int getTop() {
- return top;
- }
-
- public int getBottom() {
- return bottom;
- }
-
- public int getWidth() {
- return right - left;
- }
-
- public int getHeight() {
- return bottom - top;
- }
-
- public int getTopChange() {
- // We need this compensation to ensure that the QS moves in sync.
- int clipTopAmountCompensation = 0;
- if (startClipTopAmount != 0.0f) {
- clipTopAmountCompensation = (int) MathUtils.lerp(0, startClipTopAmount,
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress));
- }
- return Math.min(top - startPosition[1] - clipTopAmountCompensation, 0);
- }
-
- public float getProgress() {
- return linearProgress;
- }
-
- public float getProgress(long delay, long duration) {
- return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
- / duration, 0.0f, 1.0f);
- }
-
- public int getStartClipTopAmount() {
- return startClipTopAmount;
- }
-
- public int getParentStartClipTopAmount() {
- return parentStartClipTopAmount;
- }
-
- public float getStartTranslationZ() {
- return startTranslationZ;
- }
-
- public float getTopCornerRadius() {
- return topCornerRadius;
- }
-
- public float getBottomCornerRadius() {
- return bottomCornerRadius;
- }
- }
-
- public interface Callback {
-
- /**
- * Called when the launch animation was cancelled.
- */
- void onLaunchAnimationCancelled();
-
- /**
- * Called when the launch animation has timed out without starting an actual animation.
- */
- void onExpandAnimationTimedOut();
-
- /**
- * Called when the expand animation has finished.
- *
- * @param launchIsFullScreen True if this launch was fullscreen, such that now the window
- * fills the whole screen
- */
- void onExpandAnimationFinished(boolean launchIsFullScreen);
-
- /**
- * Are animations currently enabled.
- */
- boolean areLaunchAnimationsEnabled();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
new file mode 100644
index 0000000..d5835fc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt
@@ -0,0 +1,44 @@
+package com.android.systemui.statusbar.notification
+
+import android.util.MathUtils
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Interpolators
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator
+import kotlin.math.min
+
+/** Parameters for the notifications expand animations. */
+class ExpandAnimationParameters(
+ top: Int,
+ bottom: Int,
+ left: Int,
+ right: Int,
+
+ topCornerRadius: Float = 0f,
+ bottomCornerRadius: Float = 0f
+) : ActivityLaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) {
+ @VisibleForTesting
+ constructor() : this(
+ top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f
+ )
+
+ var startTranslationZ = 0f
+ var startClipTopAmount = 0
+ var parentStartClipTopAmount = 0
+ var progress = 0f
+ var linearProgress = 0f
+
+ override val topChange: Int
+ get() {
+ // We need this compensation to ensure that the QS moves in sync.
+ var clipTopAmountCompensation = 0
+ if (startClipTopAmount.toFloat() != 0.0f) {
+ clipTopAmountCompensation = MathUtils.lerp(0f, startClipTopAmount.toFloat(),
+ Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress)).toInt()
+ }
+ return min(super.topChange - clipTopAmountCompensation, 0)
+ }
+
+ fun getProgress(delay: Long, duration: Long): Float {
+ return ActivityLaunchAnimator.getProgress(linearProgress, delay, duration)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
new file mode 100644
index 0000000..2f966b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -0,0 +1,136 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.statusbar.NotificationShadeDepthController
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.stack.NotificationListContainer
+import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator
+import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
+import kotlin.math.ceil
+import kotlin.math.max
+
+/** A provider of [NotificationLaunchAnimatorController]. */
+class NotificationLaunchAnimatorControllerProvider(
+ private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
+ private val notificationPanelViewController: NotificationPanelViewController,
+ private val notificationListContainer: NotificationListContainer,
+ private val depthController: NotificationShadeDepthController
+) {
+ fun getAnimatorController(
+ notification: ExpandableNotificationRow
+ ): NotificationLaunchAnimatorController {
+ return NotificationLaunchAnimatorController(
+ notificationShadeWindowViewController,
+ notificationPanelViewController,
+ notificationListContainer,
+ depthController,
+ notification
+ )
+ }
+}
+
+/**
+ * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance
+ * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a
+ * notification expanding into an opening window.
+ */
+class NotificationLaunchAnimatorController(
+ private val notificationShadeWindowViewController: NotificationShadeWindowViewController,
+ private val notificationPanelViewController: NotificationPanelViewController,
+ private val notificationListContainer: NotificationListContainer,
+ private val depthController: NotificationShadeDepthController,
+ private val notification: ExpandableNotificationRow
+) : ActivityLaunchAnimator.Controller {
+ override fun getRootView(): View = notification.rootView
+
+ override fun createAnimatorState(): ActivityLaunchAnimator.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
+
+ val params = ExpandAnimationParameters(
+ top = location[1],
+ bottom = location[1] + height,
+ left = location[0],
+ right = location[0] + notification.width,
+ topCornerRadius = notification.currentBackgroundRadiusTop,
+ bottomCornerRadius = notification.currentBackgroundRadiusBottom
+ )
+
+ params.startTranslationZ = notification.translationZ
+ params.startClipTopAmount = notification.clipTopAmount
+ if (notification.isChildInGroup) {
+ val parentClip = notification.notificationParent.clipTopAmount
+ params.parentStartClipTopAmount = parentClip
+
+ // We need to calculate how much the child is clipped by the parent because children
+ // always have 0 clipTopAmount
+ if (parentClip != 0) {
+ val childClip = parentClip - notification.translationY
+ if (childClip > 0) {
+ params.startClipTopAmount = ceil(childClip.toDouble()).toInt()
+ }
+ }
+ }
+
+ return params
+ }
+
+ override fun onIntentStarted(willAnimate: Boolean) {
+ notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate)
+ }
+
+ override fun onLaunchAnimationCancelled() {
+ // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
+ // here?
+ notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ }
+
+ override fun onLaunchAnimationTimedOut() {
+ notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ }
+
+ override fun onLaunchAnimationAborted() {
+ notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ notificationPanelViewController.setLaunchingNotification(true)
+ notification.isExpandAnimationRunning = true
+ notificationListContainer.setExpandingNotification(notification)
+
+ InteractionJankMonitor.getInstance().begin(notification,
+ InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ InteractionJankMonitor.getInstance().end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
+
+ notificationPanelViewController.setLaunchingNotification(false)
+ notification.isExpandAnimationRunning = false
+ notificationShadeWindowViewController.setExpandAnimationRunning(false)
+ notificationListContainer.setExpandingNotification(null)
+ applyParams(null)
+ }
+
+ private fun applyParams(params: ExpandAnimationParameters?) {
+ notification.applyExpandAnimationParams(params)
+ notificationListContainer.applyExpandAnimationParams(params)
+ notificationPanelViewController.applyExpandAnimationParams(params)
+ depthController.notificationLaunchAnimationParams = params
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: ActivityLaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ val params = state as ExpandAnimationParameters
+ params.progress = progress
+ params.linearProgress = linearProgress
+
+ applyParams(params)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index a3a4014..207a894 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -19,7 +19,6 @@
import static android.app.Notification.Action.SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -79,6 +78,7 @@
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -86,7 +86,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -2045,9 +2045,8 @@
float extraWidthForClipping = params.getWidth() - getWidth();
setExtraWidthForClipping(extraWidthForClipping);
int top = params.getTop();
- float interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(params.getProgress());
int startClipTopAmount = params.getStartClipTopAmount();
- int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, interpolation);
+ int clipTopAmount = (int) MathUtils.lerp(startClipTopAmount, 0, params.getProgress());
if (mNotificationParent != null) {
float parentY = mNotificationParent.getTranslationY();
top -= parentY;
@@ -2096,7 +2095,7 @@
if (expandAnimationRunning) {
contentView.animate()
.alpha(0f)
- .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT)
+ .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT)
.setInterpolator(Interpolators.ALPHA_OUT);
setAboveShelf(true);
mExpandAnimationRunning = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
index 9588563..07d1e68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java
@@ -31,7 +31,8 @@
import com.android.internal.util.ArrayUtils;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
/**
* A view that can be used for both the dimmed and normal background of an notification.
@@ -277,13 +278,14 @@
invalidate();
}
- public void setExpandAnimationParams(ActivityLaunchAnimator.ExpandAnimationParameters params) {
+ /** Set the current expand animation parameters. */
+ public void setExpandAnimationParams(ExpandAnimationParameters params) {
mActualHeight = params.getHeight();
mActualWidth = params.getWidth();
float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
params.getProgress(
- ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT /* delay */,
- ActivityLaunchAnimator.ANIMATION_DURATION_FADE_APP /* duration */));
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_OUT_CONTENT /* delay */,
+ ActivityLaunchAnimator.ANIMATION_DURATION_FADE_IN_WINDOW /* duration */));
mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
invalidate();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
index 72f3216..2a2e733f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java
@@ -16,14 +16,13 @@
package com.android.systemui.statusbar.notification.stack;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 40c0b89..ad06e7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
@@ -91,6 +90,7 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.FakeShadowView;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationUtils;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7baad1c..ce7b397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -75,8 +75,8 @@
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.ForegroundServiceDismissalFeatureController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -1497,8 +1497,7 @@
}
@Override
- public void applyExpandAnimationParams(
- ActivityLaunchAnimator.ExpandAnimationParameters params) {
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
mView.applyExpandAnimationParams(params);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 555df5c..0cad113 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -26,7 +26,6 @@
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE;
import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static java.lang.Float.isNaN;
@@ -100,6 +99,7 @@
import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -117,10 +117,10 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -178,6 +178,10 @@
* Fling until QS is completely hidden.
*/
private static final int FLING_HIDE = 2;
+ private static final long ANIMATION_DELAY_ICON_FADE_IN =
+ ActivityLaunchAnimator.ANIMATION_DURATION - CollapsedStatusBarFragment.FADE_IN_DURATION
+ - CollapsedStatusBarFragment.FADE_IN_DELAY - 16;
+
private final DozeParameters mDozeParameters;
private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
private final OnClickListener mOnClickListener = new OnClickListener();
@@ -3208,8 +3212,7 @@
return;
}
- boolean hideIcons = params.getProgress(
- ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+ boolean hideIcons = params.getProgress(ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
if (hideIcons != mHideIconsDuringNotificationLaunch) {
mHideIconsDuringNotificationLaunch = hideIcons;
if (!hideIcons) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index d074e64..ee78c00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -527,6 +527,11 @@
}
@Override
+ public boolean isLaunchingActivity() {
+ return mCurrentState.mLaunchingActivity;
+ }
+
+ @Override
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;
apply(mCurrentState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index 2ff7c99..72f7ff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -92,7 +92,6 @@
private View mBrightnessMirror;
private boolean mTouchActive;
private boolean mTouchCancelled;
- private boolean mExpandAnimationPending;
private boolean mExpandAnimationRunning;
private NotificationStackScrollLayout mStackScrollLayout;
private PhoneStatusBarView mStatusBarView;
@@ -235,7 +234,7 @@
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
setTouchActive(false);
}
- if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
+ if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
mFalsingCollector.onTouchEvent(ev);
@@ -435,8 +434,6 @@
}
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.print(" mExpandAnimationPending=");
- pw.println(mExpandAnimationPending);
pw.print(" mExpandAnimationRunning=");
pw.println(mExpandAnimationRunning);
pw.print(" mTouchCancelled=");
@@ -445,19 +442,10 @@
pw.println(mTouchActive);
}
- public void setExpandAnimationPending(boolean pending) {
- if (mExpandAnimationPending != pending) {
- mExpandAnimationPending = pending;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
- }
- }
-
public void setExpandAnimationRunning(boolean running) {
if (mExpandAnimationRunning != running) {
mExpandAnimationRunning = running;
- mNotificationShadeWindowController
- .setLaunchingActivity(mExpandAnimationPending | mExpandAnimationRunning);
+ mNotificationShadeWindowController.setLaunchingActivity(mExpandAnimationRunning);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8ed9cd6..5fd0d66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -173,6 +173,7 @@
import com.android.systemui.plugins.OverlayPlugin;
import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -208,9 +209,9 @@
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.charging.ChargingRippleView;
import com.android.systemui.statusbar.charging.WiredChargingRippleController;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
+import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
@@ -263,7 +264,7 @@
ActivityStarter, KeyguardStateController.Callback,
OnHeadsUpChangedListener, CommandQueue.Callbacks,
ColorExtractor.OnColorsChangedListener, ConfigurationListener,
- StatusBarStateController.StateListener, ActivityLaunchAnimator.Callback,
+ StatusBarStateController.StateListener,
LifecycleOwner, BatteryController.BatteryStateChangeCallback {
public static final boolean MULTIUSER_DEBUG = false;
@@ -692,6 +693,7 @@
private boolean mVibrateOnOpening;
private final VibratorHelper mVibratorHelper;
private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
protected StatusBarNotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
@@ -1370,16 +1372,18 @@
private void setUpPresenter() {
// Set up the initial notification state.
- mActivityLaunchAnimator = new ActivityLaunchAnimator(
- mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
- mNotificationShadeDepthControllerLazy.get(),
+ mActivityLaunchAnimator = new ActivityLaunchAnimator();
+ mNotificationAnimationProvider = new NotificationLaunchAnimatorControllerProvider(
+ mNotificationShadeWindowViewController,
+ mNotificationPanelViewController,
mStackScrollerController.getNotificationListContainer(),
- mContext.getMainExecutor());
+ mNotificationShadeDepthControllerLazy.get()
+ );
// TODO: inject this.
mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
mHeadsUpManager, mNotificationShadeWindowView, mStackScrollerController,
- mDozeScrimController, mScrimController, mActivityLaunchAnimator,
+ mDozeScrimController, mScrimController, mNotificationShadeWindowController,
mDynamicPrivacyController, mKeyguardStateController,
mKeyguardIndicationController,
this /* statusBar */, mShadeController, mCommandQueue, mInitController,
@@ -1392,6 +1396,7 @@
mStatusBarNotificationActivityStarterBuilder
.setStatusBar(this)
.setActivityLaunchAnimator(mActivityLaunchAnimator)
+ .setNotificationAnimatorControllerProvider(mNotificationAnimationProvider)
.setNotificationPresenter(mPresenter)
.setNotificationPanelViewController(mNotificationPanelViewController)
.build();
@@ -1990,16 +1995,16 @@
return mHeadsUpAppearanceController.shouldBeVisible();
}
+ /** A launch animation was cancelled. */
//TODO: These can / should probably be moved to NotificationPresenter or ShadeController
- @Override
public void onLaunchAnimationCancelled() {
if (!mPresenter.isCollapsing()) {
onClosingFinished();
}
}
- @Override
- public void onExpandAnimationFinished(boolean launchIsFullScreen) {
+ /** A launch animation ended. */
+ public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
if (!mPresenter.isCollapsing()) {
onClosingFinished();
}
@@ -2008,19 +2013,19 @@
}
}
- @Override
- public void onExpandAnimationTimedOut() {
+ /** A launch animation timed out. */
+ public void onLaunchAnimationTimedOut(boolean isLaunchForActivity) {
if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
- && mActivityLaunchAnimator != null
- && !mActivityLaunchAnimator.isLaunchForActivity()) {
+ && isLaunchForActivity) {
onClosingFinished();
} else {
mShadeController.collapsePanel(true /* animate */);
}
}
- @Override
+ /** Whether we should animate an activity launch. */
public boolean areLaunchAnimationsEnabled() {
+ // TODO(b/184121838): Support lock screen launch animations.
return mState == StatusBarState.SHADE;
}
@@ -3134,8 +3139,15 @@
}
@Override
- public void postStartActivityDismissingKeyguard(final PendingIntent intent) {
- mHandler.post(() -> startPendingIntentDismissingKeyguard(intent));
+ public void postStartActivityDismissingKeyguard(PendingIntent intent) {
+ postStartActivityDismissingKeyguard(intent, null /* animationController */);
+ }
+
+ @Override
+ public void postStartActivityDismissingKeyguard(final PendingIntent intent,
+ @Nullable ActivityLaunchAnimator.Controller animationController) {
+ mHandler.post(() -> startPendingIntentDismissingKeyguard(intent,
+ null /* intentSentUiThreadCallback */, animationController));
}
@Override
@@ -3618,6 +3630,23 @@
mShadeController.runPostCollapseRunnables();
}
+ /**
+ * Collapse the panel directly if we are on the main thread, post the collapsing on the main
+ * thread if we are not.
+ */
+ void collapsePanelOnMainThread() {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ mShadeController.collapsePanel();
+ } else {
+ mContext.getMainExecutor().execute(mShadeController::collapsePanel);
+ }
+ }
+
+ /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
+ void collapsePanelWithDuration(int duration) {
+ mNotificationPanelViewController.collapseWithDuration(duration);
+ }
+
@Override
public void onStatePreChange(int oldState, int newState) {
// If we're visible and switched to SHADE_LOCKED (the user dragged
@@ -4415,7 +4444,14 @@
KeyboardShortcuts.dismiss();
}
- public void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone) {
+ /**
+ * Dismiss the keyguard then execute an action.
+ *
+ * @param action The action to execute after dismissing the keyguard.
+ * @param collapsePanel Whether we should collapse the panel after dismissing the keyguard.
+ */
+ private void executeActionDismissingKeyguard(Runnable action, boolean afterKeyguardGone,
+ boolean collapsePanel) {
if (!mDeviceProvisionedController.isDeviceProvisioned()) return;
dismissKeyguardThenExecute(() -> {
@@ -4431,7 +4467,8 @@
action.run();
}).start();
- return mShadeController.collapsePanel();
+ boolean deferred = collapsePanel ? mShadeController.collapsePanel() : false;
+ return deferred;
}, afterKeyguardGone);
}
@@ -4443,27 +4480,55 @@
@Override
public void startPendingIntentDismissingKeyguard(
final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback) {
- startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback, null /* row */);
+ startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback,
+ (ActivityLaunchAnimator.Controller) null);
+ }
+
+ @Override
+ public void startPendingIntentDismissingKeyguard(PendingIntent intent,
+ Runnable intentSentUiThreadCallback, View associatedView) {
+ ActivityLaunchAnimator.Controller animationController = null;
+ if (associatedView instanceof ExpandableNotificationRow) {
+ animationController = mNotificationAnimationProvider.getAnimatorController(
+ ((ExpandableNotificationRow) associatedView));
+ }
+
+ startPendingIntentDismissingKeyguard(intent, intentSentUiThreadCallback,
+ animationController);
}
@Override
public void startPendingIntentDismissingKeyguard(
final PendingIntent intent, @Nullable final Runnable intentSentUiThreadCallback,
- View associatedView) {
+ @Nullable ActivityLaunchAnimator.Controller animationController) {
final boolean afterKeyguardGone = intent.isActivity()
&& mActivityIntentHelper.wouldLaunchResolverActivity(intent.getIntent(),
mLockscreenUserManager.getCurrentUserId());
+ boolean animate =
+ animationController != null && areLaunchAnimationsEnabled() && !isOccluded();
+ boolean collapse = !animate;
executeActionDismissingKeyguard(() -> {
try {
- intent.send(null, 0, null, null, null, null, getActivityOptions(
- mDisplayId,
- mActivityLaunchAnimator.getLaunchAnimation(associatedView, isOccluded())));
+ // We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
+ // shade is collapsed after the animation (or when it is cancelled, aborted, etc).
+ ActivityLaunchAnimator.Controller controller =
+ animate ? new StatusBarLaunchAnimatorController(animationController, this,
+ intent.isActivity())
+ : null;
+
+ mActivityLaunchAnimator.startPendingIntentWithAnimation(
+ controller,
+ (animationAdapter) -> intent.sendAndReturnResult(null, 0, null, null, null,
+ null, getActivityOptions(mDisplayId, animationAdapter)));
} catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
Log.w(TAG, "Sending intent failed: " + e);
-
+ if (!collapse) {
+ // executeActionDismissingKeyguard did not collapse for us already.
+ collapsePanelOnMainThread();
+ }
// TODO: Dismiss Keyguard.
}
if (intent.isActivity()) {
@@ -4472,7 +4537,7 @@
if (intentSentUiThreadCallback != null) {
postOnUiThread(intentSentUiThreadCallback);
}
- }, afterKeyguardGone);
+ }, afterKeyguardGone, collapse);
}
private void postOnUiThread(Runnable runnable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
new file mode 100644
index 0000000..d45f64f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -0,0 +1,47 @@
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator
+
+/**
+ * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right
+ * time.
+ */
+class StatusBarLaunchAnimatorController(
+ private val delegate: ActivityLaunchAnimator.Controller,
+ private val statusBar: StatusBar,
+ private val isLaunchForActivity: Boolean = true
+) : ActivityLaunchAnimator.Controller by delegate {
+ override fun onIntentStarted(willAnimate: Boolean) {
+ delegate.onIntentStarted(willAnimate)
+ if (!willAnimate) {
+ statusBar.collapsePanelOnMainThread()
+ }
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ if (!isExpandingFullyAbove) {
+ statusBar.collapsePanelWithDuration(ActivityLaunchAnimator.ANIMATION_DURATION.toInt())
+ }
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
+ statusBar.onLaunchAnimationEnd(isExpandingFullyAbove)
+ }
+
+ override fun onLaunchAnimationCancelled() {
+ delegate.onLaunchAnimationCancelled()
+ statusBar.onLaunchAnimationCancelled()
+ }
+
+ override fun onLaunchAnimationTimedOut() {
+ delegate.onLaunchAnimationTimedOut()
+ statusBar.onLaunchAnimationTimedOut(isLaunchForActivity)
+ }
+
+ override fun onLaunchAnimationAborted() {
+ delegate.onLaunchAnimationAborted()
+ statusBar.collapsePanelOnMainThread()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 801ac96..2e918da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -21,7 +21,6 @@
import static com.android.systemui.statusbar.phone.StatusBar.getActivityOptions;
import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -40,7 +39,6 @@
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.EventLog;
-import android.view.RemoteAnimationAdapter;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
@@ -52,6 +50,7 @@
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
@@ -60,11 +59,10 @@
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
@@ -124,6 +122,7 @@
private final NotificationPresenter mPresenter;
private final NotificationPanelViewController mNotificationPanel;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
private final OnUserInteractionCallback mOnUserInteractionCallback;
private boolean mIsCollapsingToShowActivityOverLockscreen;
@@ -162,7 +161,8 @@
StatusBar statusBar,
NotificationPresenter presenter,
NotificationPanelViewController panel,
- ActivityLaunchAnimator activityLaunchAnimator) {
+ ActivityLaunchAnimator activityLaunchAnimator,
+ NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
mContext = context;
mCommandQueue = commandQueue;
mMainThreadHandler = mainThreadHandler;
@@ -198,6 +198,7 @@
mPresenter = presenter;
mNotificationPanel = panel;
mActivityLaunchAnimator = activityLaunchAnimator;
+ mNotificationAnimationProvider = notificationAnimationProvider;
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@@ -400,17 +401,15 @@
}
if (Looper.getMainLooper().isCurrentThread()) {
- mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
+ expandBubbleStack(entry);
} else {
- mMainThreadHandler.post(
- () -> mBubblesManagerOptional.get().expandStackAndSelectBubble(entry));
+ mMainThreadHandler.post(() -> expandBubbleStack(entry));
}
+ }
- // expandStackAndSelectBubble won't affect shouldCollapse, so we can collapse directly even
- // if we are not on the main thread.
- if (shouldCollapse()) {
- collapseOnMainThread();
- }
+ private void expandBubbleStack(NotificationEntry entry) {
+ mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
+ mShadeController.collapsePanel();
}
private void startNotificationIntent(
@@ -420,32 +419,36 @@
ExpandableNotificationRow row,
boolean wasOccluded,
boolean isActivityIntent) {
- RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
- wasOccluded);
mLogger.logStartNotificationIntent(entry.getKey(), intent);
try {
- if (adapter != null) {
- ActivityTaskManager.getService()
- .registerRemoteAnimationForNextActivityStart(
- intent.getCreatorPackage(), adapter);
+ ActivityLaunchAnimator.Controller animationController = null;
+ if (!wasOccluded && mStatusBar.areLaunchAnimationsEnabled()) {
+ animationController = new StatusBarLaunchAnimatorController(
+ mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
+ isActivityIntent);
}
- long eventTime = row.getAndResetLastActionUpTime();
- Bundle options = eventTime > 0
- ? getActivityOptions(
- mStatusBar.getDisplayId(),
- adapter,
- mKeyguardStateController.isShowing(),
- eventTime)
- : getActivityOptions(mStatusBar.getDisplayId(), adapter);
- int launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
- null, null, options);
- mMainThreadHandler.post(() -> {
- mActivityLaunchAnimator.setLaunchResult(launchResult, isActivityIntent);
- if (shouldCollapse()) {
- collapseOnMainThread();
- }
- });
- } catch (RemoteException | PendingIntent.CanceledException e) {
+
+ mActivityLaunchAnimator.startPendingIntentWithAnimation(animationController,
+ (adapter) -> {
+ long eventTime = row.getAndResetLastActionUpTime();
+ Bundle options = eventTime > 0
+ ? getActivityOptions(
+ mStatusBar.getDisplayId(),
+ adapter,
+ mKeyguardStateController.isShowing(),
+ eventTime)
+ : getActivityOptions(mStatusBar.getDisplayId(), adapter);
+ return intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+ null, null, options);
+ });
+
+ // Note that other cases when we should still collapse (like activity already on top) is
+ // handled by the StatusBarLaunchAnimatorController.
+ boolean shouldCollapse = animationController == null;
+ if (shouldCollapse) {
+ collapseOnMainThread();
+ }
+ } catch (PendingIntent.CanceledException e) {
// the stack trace isn't very helpful here.
// Just log the exception message.
mLogger.logSendingIntentFailed(e);
@@ -458,20 +461,30 @@
ExpandableNotificationRow row) {
mActivityStarter.dismissKeyguardThenExecute(() -> {
AsyncTask.execute(() -> {
- int launchResult = TaskStackBuilder.create(mContext)
- .addNextIntentWithParentStack(intent)
- .startActivities(getActivityOptions(
- mStatusBar.getDisplayId(),
- mActivityLaunchAnimator.getLaunchAnimation(
- row, mStatusBar.isOccluded())),
- new UserHandle(UserHandle.getUserId(appUid)));
+ ActivityLaunchAnimator.Controller animationController = null;
+ if (!mStatusBar.isOccluded() && mStatusBar.areLaunchAnimationsEnabled()) {
+ animationController = new StatusBarLaunchAnimatorController(
+ mNotificationAnimationProvider.getAnimatorController(row), mStatusBar,
+ true /* isActivityIntent */);
+ }
+
+ mActivityLaunchAnimator.startIntentWithAnimation(
+ animationController,
+ (adapter) -> TaskStackBuilder.create(mContext)
+ .addNextIntentWithParentStack(intent)
+ .startActivities(getActivityOptions(
+ mStatusBar.getDisplayId(),
+ adapter),
+ new UserHandle(UserHandle.getUserId(appUid))));
+
+ // Note that other cases when we should still collapse (like activity already on
+ // top) is handled by the StatusBarLaunchAnimatorController.
+ boolean shouldCollapse = animationController == null;
// Putting it back on the main thread, since we're touching views
mMainThreadHandler.post(() -> {
- mActivityLaunchAnimator.setLaunchResult(launchResult,
- true /* isActivityIntent */);
removeHUN(row);
- if (shouldCollapse()) {
+ if (shouldCollapse) {
mCommandQueue.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */);
}
@@ -494,11 +507,10 @@
tsb.addNextIntent(intent);
}
tsb.startActivities(null, UserHandle.CURRENT);
- if (shouldCollapse()) {
- // Putting it back on the main thread, since we're touching views
- mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
- CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
- }
+
+ // Putting it back on the main thread, since we're touching views
+ mMainThreadHandler.post(() -> mCommandQueue.animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
});
return true;
}, null, false /* afterKeyguardGone */);
@@ -576,11 +588,6 @@
}
}
- private boolean shouldCollapse() {
- return mStatusBarStateController.getState() != StatusBarState.SHADE
- || !mActivityLaunchAnimator.isAnimationPending();
- }
-
private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
if (mPresenter.isDeviceInVrMode()) {
return true;
@@ -639,6 +646,7 @@
private NotificationPresenter mNotificationPresenter;
private NotificationPanelViewController mNotificationPanelViewController;
private ActivityLaunchAnimator mActivityLaunchAnimator;
+ private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@Inject
public Builder(
@@ -714,12 +722,20 @@
return this;
}
+ /** Set the ActivityLaunchAnimator. */
public Builder setActivityLaunchAnimator(ActivityLaunchAnimator activityLaunchAnimator) {
mActivityLaunchAnimator = activityLaunchAnimator;
return this;
}
- /** Set the NotificationPanelViewController */
+ /** Set the NotificationLaunchAnimatorControllerProvider. */
+ public Builder setNotificationAnimatorControllerProvider(
+ NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
+ mNotificationAnimationProvider = notificationAnimationProvider;
+ return this;
+ }
+
+ /** Set the NotificationPanelViewController. */
public Builder setNotificationPanelViewController(
NotificationPanelViewController notificationPanelViewController) {
mNotificationPanelViewController = notificationPanelViewController;
@@ -759,7 +775,8 @@
mStatusBar,
mNotificationPresenter,
mNotificationPanelViewController,
- mActivityLaunchAnimator);
+ mActivityLaunchAnimator,
+ mNotificationAnimationProvider);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 94edd1e..088f947 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -57,7 +57,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -117,7 +116,7 @@
private final AccessibilityManager mAccessibilityManager;
private final KeyguardManager mKeyguardManager;
- private final ActivityLaunchAnimator mActivityLaunchAnimator;
+ private final NotificationShadeWindowController mNotificationShadeWindowController;
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private boolean mReinflateNotificationsOnUserSwitched;
@@ -133,7 +132,7 @@
NotificationStackScrollLayoutController stackScrollerController,
DozeScrimController dozeScrimController,
ScrimController scrimController,
- ActivityLaunchAnimator activityLaunchAnimator,
+ NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
@@ -152,7 +151,7 @@
mShadeController = shadeController;
mCommandQueue = commandQueue;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
- mActivityLaunchAnimator = activityLaunchAnimator;
+ mNotificationShadeWindowController = notificationShadeWindowController;
mAboveShelfObserver.setListener(statusBarWindow.findViewById(
R.id.notification_container_parent));
mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
@@ -272,8 +271,7 @@
@Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
- || mActivityLaunchAnimator.isAnimationPending()
- || mActivityLaunchAnimator.isAnimationRunning();
+ || mNotificationShadeWindowController.isLaunchingActivity();
}
private void maybeEndAmbientPulse() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 603d423..46611e0 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -50,6 +50,8 @@
private var desiredMeasureWidth = 0
private var desiredMeasureHeight = 0
+ private var transitionVisibility = View.VISIBLE
+
/**
* The measured state of this view which is the one we will lay ourselves out with. This
* may differ from the currentState if there is an external animation or transition running.
@@ -81,6 +83,13 @@
}
}
+ override fun setTransitionVisibility(visibility: Int) {
+ // We store the last transition visibility assigned to this view to restore it later if
+ // necessary.
+ super.setTransitionVisibility(visibility)
+ transitionVisibility = visibility
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
val childCount = childCount
@@ -162,7 +171,16 @@
updateBounds()
translationX = currentState.translation.x
translationY = currentState.translation.y
+
CrossFadeHelper.fadeIn(this, currentState.alpha)
+
+ // CrossFadeHelper#fadeIn will change this view visibility, which overrides the transition
+ // visibility. We set the transition visibility again to make sure that this view plays well
+ // with GhostView, which sets the transition visibility and is used for activity launch
+ // animations.
+ if (transitionVisibility != View.VISIBLE) {
+ setTransitionVisibility(transitionVisibility)
+ }
}
private fun applyCurrentStateOnPredraw() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt
new file mode 100644
index 0000000..722b0b1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt
@@ -0,0 +1,188 @@
+package com.android.systemui.plugins.animation
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationAdapter
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNotNull
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import junit.framework.AssertionFailedError
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import kotlin.concurrent.thread
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ActivityLaunchAnimatorTest : SysuiTestCase() {
+ private val activityLaunchAnimator = ActivityLaunchAnimator()
+ private val rootView = View(mContext)
+ @Spy private val controller = TestLaunchAnimatorController(rootView)
+ @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback
+
+ @get:Rule val rule = MockitoJUnit.rule()
+
+ private fun startIntentWithAnimation(
+ controller: ActivityLaunchAnimator.Controller? = this.controller,
+ intentStarter: (RemoteAnimationAdapter?) -> Int
+ ) {
+ // We start in a new thread so that we can ensure that the callbacks are called in the main
+ // thread.
+ thread {
+ activityLaunchAnimator.startIntentWithAnimation(controller, intentStarter)
+ }.join()
+ }
+
+ @Test
+ fun animationAdapterIsNullIfControllerIsNull() {
+ var startedIntent = false
+ var animationAdapter: RemoteAnimationAdapter? = null
+
+ startIntentWithAnimation(controller = null) { adapter ->
+ startedIntent = true
+ animationAdapter = adapter
+
+ ActivityManager.START_SUCCESS
+ }
+
+ assertTrue(startedIntent)
+ assertNull(animationAdapter)
+ }
+
+ @Test
+ fun animatesIfActivityOpens() {
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ var animationAdapter: RemoteAnimationAdapter? = null
+ startIntentWithAnimation { adapter ->
+ animationAdapter = adapter
+ ActivityManager.START_SUCCESS
+ }
+
+ assertNotNull(animationAdapter)
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertTrue(willAnimateCaptor.value)
+ }
+
+ @Test
+ fun doesNotAnimateIfActivityIsAlreadyOpen() {
+ val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java)
+ startIntentWithAnimation { ActivityManager.START_DELIVERED_TO_TOP }
+
+ waitForIdleSync()
+ verify(controller).onIntentStarted(willAnimateCaptor.capture())
+ assertFalse(willAnimateCaptor.value)
+ }
+
+ @Test
+ fun doesNotStartIfAnimationIsCancelled() {
+ val runner = ActivityLaunchAnimator.Runner(controller)
+ runner.onAnimationCancelled()
+ runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+
+ waitForIdleSync()
+ verify(controller).onLaunchAnimationCancelled()
+ verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+ }
+
+ @Test
+ fun abortsIfNoOpeningWindowIsFound() {
+ val runner = ActivityLaunchAnimator.Runner(controller)
+ runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback)
+
+ waitForIdleSync()
+ verify(controller).onLaunchAnimationAborted()
+ verify(controller, never()).onLaunchAnimationStart(anyBoolean())
+ }
+
+ @Test
+ fun startsAnimationIfWindowIsOpening() {
+ val runner = ActivityLaunchAnimator.Runner(controller)
+ runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback)
+ waitForIdleSync()
+ verify(controller).onLaunchAnimationStart(anyBoolean())
+ }
+
+ private fun fakeWindow() = RemoteAnimationTarget(
+ 0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0,
+ Point(), Rect(), Rect(), WindowConfiguration(), false, SurfaceControl(), Rect(),
+ ActivityManager.RunningTaskInfo()
+ )
+}
+
+/**
+ * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called
+ * outside of the main thread.
+ */
+private class TestLaunchAnimatorController(
+ private val rootView: View
+) : ActivityLaunchAnimator.Controller {
+ override fun getRootView(): View = rootView
+
+ override fun createAnimatorState() = ActivityLaunchAnimator.State(
+ top = 100,
+ bottom = 200,
+ left = 300,
+ right = 400,
+ topCornerRadius = 10f,
+ bottomCornerRadius = 20f
+ )
+
+ private fun assertOnMainThread() {
+ if (Looper.myLooper() != Looper.getMainLooper()) {
+ throw AssertionFailedError("Called outside of main thread.")
+ }
+ }
+
+ override fun onIntentStarted(willAnimate: Boolean) {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationProgress(
+ state: ActivityLaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationCancelled() {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationTimedOut() {
+ assertOnMainThread()
+ }
+
+ override fun onLaunchAnimationAborted() {
+ assertOnMainThread()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index e65db5e..fd94812 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -27,7 +27,7 @@
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
+import com.android.systemui.statusbar.notification.ExpandAnimationParameters
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -215,7 +215,7 @@
fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
`when`(shadeSpring.radius).thenReturn(maxBlur)
`when`(shadeAnimation.radius).thenReturn(maxBlur)
- val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ val animProgress = ExpandAnimationParameters()
animProgress.linearProgress = 1f
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
notificationShadeDepthController.updateBlurCallback.doFrame(0)
@@ -264,7 +264,7 @@
@Test
fun setNotificationLaunchAnimationParams_schedulesFrame() {
- val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ val animProgress = ExpandAnimationParameters()
animProgress.linearProgress = 0.5f
notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
verify(choreographer).postFrameCallback(
@@ -273,7 +273,7 @@
@Test
fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
- val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+ val animProgress = ExpandAnimationParameters()
animProgress.linearProgress = 0.5f
`when`(shadeSpring.radius).thenReturn(0)
`when`(shadeAnimation.radius).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
deleted file mode 100644
index 2fa6cf0..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2018 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.statusbar.notification;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.RemoteAnimationAdapter;
-import android.view.View;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ActivityLaunchAnimatorTest extends SysuiTestCase {
-
- private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
- private ActivityLaunchAnimator mLaunchAnimator;
- @Mock
- private ActivityLaunchAnimator.Callback mCallback;
- @Mock
- private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
- @Mock
- private NotificationShadeWindowView mNotificationShadeWindowView;
- @Mock
- private NotificationListContainer mNotificationContainer;
- @Mock
- private ExpandableNotificationRow mRow;
- @Mock
- private NotificationShadeDepthController mNotificationShadeDepthController;
- @Mock
- private NotificationPanelViewController mNotificationPanelViewController;
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- @Before
- public void setUp() throws Exception {
- when(mNotificationShadeWindowViewController.getView())
- .thenReturn(mNotificationShadeWindowView);
- when(mNotificationShadeWindowView.getResources()).thenReturn(mContext.getResources());
- when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true);
- mLaunchAnimator = new ActivityLaunchAnimator(
- mNotificationShadeWindowViewController,
- mCallback,
- mNotificationPanelViewController,
- mNotificationShadeDepthController,
- mNotificationContainer,
- mExecutor);
- }
-
- @Test
- public void testReturnsNullIfNotEnabled() {
- when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false);
- RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow,
- false /* occluded */);
- Assert.assertTrue("The LaunchAnimator generated an animation even though animations are "
- + "disabled", launchAnimation == null);
- }
-
- @Test
- public void testNotWorkingWhenOccluded() {
- when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false);
- RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow,
- true /* occluded */);
- Assert.assertTrue("The LaunchAnimator generated an animation even though we're occluded",
- launchAnimation == null);
- }
-
- @Test
- public void testTimeoutCalled() {
- RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow,
- false /* occluded */);
- Assert.assertTrue("No animation generated", launchAnimation != null);
- executePostsImmediately(mNotificationShadeWindowView);
- mLaunchAnimator.setLaunchResult(ActivityManager.START_SUCCESS,
- true /* wasIntentActivity */);
- verify(mCallback).onExpandAnimationTimedOut();
- }
-
- private void executePostsImmediately(View view) {
- doAnswer((i) -> {
- Runnable run = i.getArgument(0);
- run.run();
- return null;
- }).when(view).post(any());
- doAnswer((i) -> {
- Runnable run = i.getArgument(0);
- run.run();
- return null;
- }).when(view).postDelayed(any(), anyLong());
- }
-}
-
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 68464ce..e34bc0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -35,7 +35,6 @@
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.PendingIntent;
-import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.RemoteException;
@@ -54,6 +53,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.animation.ActivityLaunchAnimator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.FeatureFlags;
@@ -63,9 +63,9 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
@@ -136,6 +136,8 @@
private OnUserInteractionCallback mOnUserInteractionCallback;
@Mock
private NotificationActivityStarter mNotificationActivityStarter;
+ @Mock
+ private ActivityLaunchAnimator mActivityLaunchAnimator;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationTestHelper mNotificationTestHelper;
@@ -213,11 +215,14 @@
mock(MetricsLogger.class),
mock(StatusBarNotificationActivityStarterLogger.class),
mOnUserInteractionCallback)
- .setStatusBar(mStatusBar)
- .setNotificationPresenter(mock(NotificationPresenter.class))
- .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
- .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
- .build();
+ .setStatusBar(mStatusBar)
+ .setNotificationPresenter(mock(NotificationPresenter.class))
+ .setNotificationPanelViewController(
+ mock(NotificationPanelViewController.class))
+ .setActivityLaunchAnimator(mActivityLaunchAnimator)
+ .setNotificationAnimatorControllerProvider(
+ mock(NotificationLaunchAnimatorControllerProvider.class))
+ .build();
// set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
@@ -254,14 +259,7 @@
// Then
verify(mShadeController, atLeastOnce()).collapsePanel();
- verify(mContentIntent).sendAndReturnResult(
- any(Context.class),
- anyInt() /* code */,
- any() /* fillInIntent */,
- any() /* PendingIntent.OnFinished */,
- any() /* Handler */,
- any() /* requiredPermission */,
- any() /* Bundle options */);
+ verify(mActivityLaunchAnimator).startPendingIntentWithAnimation(eq(null), any());
verify(mAssistManager).hideAssist();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index c0ebfad..8601de5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -47,7 +47,6 @@
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -125,7 +124,7 @@
mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
notificationShadeWindowView, stackScrollLayoutController,
mock(DozeScrimController.class), mock(ScrimController.class),
- mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
+ mock(NotificationShadeWindowController.class), mock(DynamicPrivacyController.class),
mock(KeyguardStateController.class),
mock(KeyguardIndicationController.class), mStatusBar,
mock(ShadeControllerImpl.class), mCommandQueue, mInitController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index 5de62b9..3d07eb1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -703,6 +703,10 @@
// ============================= Smart Action tests ============================================
// =============================================================================================
+ private View anyView() {
+ return any();
+ }
+
@Test
public void testTapSmartAction_waitsForKeyguard() throws InterruptedException {
setSmartActions(TEST_ACTION_TITLES);
@@ -710,7 +714,7 @@
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
- any());
+ anyView());
}
@Test
@@ -721,7 +725,8 @@
mView.getChildAt(2).performClick();
- verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(), any());
+ verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(),
+ anyView());
}
@Test
@@ -734,7 +739,7 @@
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1))
- .startPendingIntentDismissingKeyguard(any(), any(), any());
+ .startPendingIntentDismissingKeyguard(any(), any(), anyView());
}
@Test
@@ -746,7 +751,7 @@
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1))
- .startPendingIntentDismissingKeyguard(any(), any(), any());
+ .startPendingIntentDismissingKeyguard(any(), any(), anyView());
}
@Test