Merge "Extract ActivityLaunchAnimator into a reusable class." into sc-dev

This commit is contained in:
Jordan Demeulenaere
2021-04-07 08:16:47 +00:00
committed by Android (Google) Code Review
30 changed files with 1382 additions and 781 deletions

View File

@@ -27,6 +27,7 @@ java_library {
srcs: [
"src/**/*.java",
"src/**/*.kt",
"bcsmartspace/src/**/*.java",
],

View File

@@ -19,6 +19,7 @@ import android.app.PendingIntent;
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 @@ public interface ActivityStarter {
* 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 @@ public interface ActivityStarter {
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,

View File

@@ -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)
}
}
}

View File

@@ -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
}
}
}

View File

@@ -20,6 +20,7 @@ import android.view.View;
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 @@ public class ActivityStarterDelegate implements ActivityStarter {
@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
@@ -102,6 +112,13 @@ public class ActivityStarterDelegate implements ActivityStarter {
starter -> starter.get().postStartActivityDismissingKeyguard(intent));
}
@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(

View File

@@ -105,9 +105,7 @@ public class CrossFadeHelper {
}
} 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);
}
}

View File

@@ -35,7 +35,7 @@ import com.android.systemui.Interpolators
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 @@ class NotificationShadeDepthController @Inject constructor(
* 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) {

View File

@@ -90,9 +90,14 @@ public interface NotificationShadeWindowController extends RemoteInputController
/** 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) {}

View File

@@ -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();
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.row;
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.R;
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.NotificationMediaManager;
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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
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;

View File

@@ -31,7 +31,8 @@ import android.view.View;
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 @@ public class NotificationBackgroundView extends View {
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();
}

View File

@@ -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;

View File

@@ -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.NotificationShelfController;
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;

View File

@@ -75,8 +75,8 @@ import com.android.systemui.statusbar.NotificationShelfController;
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 @@ public class NotificationStackScrollLayoutController {
}
@Override
public void applyExpandAnimationParams(
ActivityLaunchAnimator.ExpandAnimationParameters params) {
public void applyExpandAnimationParams(ExpandAnimationParameters params) {
mView.applyExpandAnimationParams(params);
}

View File

@@ -26,7 +26,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_
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.fragments.FragmentHostManager.FragmentListener;
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.RemoteInputController;
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 @@ public class NotificationPanelViewController extends PanelViewController {
* 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 @@ public class NotificationPanelViewController extends PanelViewController {
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) {

View File

@@ -526,6 +526,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
apply(mCurrentState);
}
@Override
public boolean isLaunchingActivity() {
return mCurrentState.mLaunchingActivity;
}
@Override
public void setScrimsVisibility(int scrimsVisibility) {
mCurrentState.mScrimsVisibility = scrimsVisibility;

View File

@@ -92,7 +92,6 @@ public class NotificationShadeWindowViewController {
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 @@ public class NotificationShadeWindowViewController {
|| ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
setTouchActive(false);
}
if (mTouchCancelled || mExpandAnimationRunning || mExpandAnimationPending) {
if (mTouchCancelled || mExpandAnimationRunning) {
return false;
}
mFalsingCollector.onTouchEvent(ev);
@@ -435,8 +434,6 @@ public class NotificationShadeWindowViewController {
}
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 @@ public class NotificationShadeWindowViewController {
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);
}
}

View File

@@ -173,6 +173,7 @@ import com.android.systemui.plugins.FalsingManager;
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.SysuiStatusBarStateController;
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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
mStatusBarNotificationActivityStarterBuilder
.setStatusBar(this)
.setActivityLaunchAnimator(mActivityLaunchAnimator)
.setNotificationAnimatorControllerProvider(mNotificationAnimationProvider)
.setNotificationPresenter(mPresenter)
.setNotificationPanelViewController(mNotificationPanelViewController)
.build();
@@ -1990,16 +1995,16 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
}
}
@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 @@ public class StatusBar extends SystemUI implements DemoMode,
}
@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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
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 @@ public class StatusBar extends SystemUI implements DemoMode,
action.run();
}).start();
return mShadeController.collapsePanel();
boolean deferred = collapsePanel ? mShadeController.collapsePanel() : false;
return deferred;
}, afterKeyguardGone);
}
@@ -4443,27 +4480,55 @@ public class StatusBar extends SystemUI implements DemoMode,
@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 @@ public class StatusBar extends SystemUI implements DemoMode,
if (intentSentUiThreadCallback != null) {
postOnUiThread(intentSentUiThreadCallback);
}
}, afterKeyguardGone);
}, afterKeyguardGone, collapse);
}
private void postOnUiThread(Runnable runnable) {

View File

@@ -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()
}
}

View File

@@ -21,7 +21,6 @@ import static android.service.notification.NotificationListenerService.REASON_CL
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.dreams.IDreamManager;
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.SysUISingleton;
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.NotificationLockscreenUserManager;
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
StatusBar statusBar,
NotificationPresenter presenter,
NotificationPanelViewController panel,
ActivityLaunchAnimator activityLaunchAnimator) {
ActivityLaunchAnimator activityLaunchAnimator,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
mContext = context;
mCommandQueue = commandQueue;
mMainThreadHandler = mainThreadHandler;
@@ -198,6 +198,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mPresenter = presenter;
mNotificationPanel = panel;
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@@ -400,17 +401,15 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
}
}
private boolean shouldCollapse() {
return mStatusBarStateController.getState() != StatusBarState.SHADE
|| !mActivityLaunchAnimator.isAnimationPending();
}
private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
if (mPresenter.isDeviceInVrMode()) {
return true;
@@ -639,6 +646,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private NotificationPresenter mNotificationPresenter;
private NotificationPanelViewController mNotificationPanelViewController;
private ActivityLaunchAnimator mActivityLaunchAnimator;
private NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@Inject
public Builder(
@@ -714,12 +722,20 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
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 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
mStatusBar,
mNotificationPresenter,
mNotificationPanelViewController,
mActivityLaunchAnimator);
mActivityLaunchAnimator,
mNotificationAnimationProvider);
}
}
}

View File

@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager;
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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
NotificationStackScrollLayoutController stackScrollerController,
DozeScrimController dozeScrimController,
ScrimController scrimController,
ActivityLaunchAnimator activityLaunchAnimator,
NotificationShadeWindowController notificationShadeWindowController,
DynamicPrivacyController dynamicPrivacyController,
KeyguardStateController keyguardStateController,
KeyguardIndicationController keyguardIndicationController,
@@ -152,7 +151,7 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
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 @@ public class StatusBarNotificationPresenter implements NotificationPresenter,
@Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
|| mActivityLaunchAnimator.isAnimationPending()
|| mActivityLaunchAnimator.isAnimationRunning();
|| mNotificationShadeWindowController.isLaunchingActivity();
}
private void maybeEndAmbientPulse() {

View File

@@ -50,6 +50,8 @@ class TransitionLayout @JvmOverloads constructor(
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 @@ class TransitionLayout @JvmOverloads constructor(
}
}
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 @@ class TransitionLayout @JvmOverloads constructor(
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() {

View File

@@ -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()
}
}

View File

@@ -27,7 +27,7 @@ import androidx.test.filters.SmallTest
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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@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 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
@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)

View File

@@ -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());
}
}

View File

@@ -35,7 +35,6 @@ import static org.mockito.Mockito.when;
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.ActivityIntentHelper;
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.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.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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
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 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
// 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();

View File

@@ -47,7 +47,6 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
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 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
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,

View File

@@ -703,6 +703,10 @@ public class SmartReplyViewTest extends SysuiTestCase {
// ============================= Smart Action tests ============================================
// =============================================================================================
private View anyView() {
return any();
}
@Test
public void testTapSmartAction_waitsForKeyguard() throws InterruptedException {
setSmartActions(TEST_ACTION_TITLES);
@@ -710,7 +714,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
any());
anyView());
}
@Test
@@ -721,7 +725,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(), any());
verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any(),
anyView());
}
@Test
@@ -734,7 +739,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1))
.startPendingIntentDismissingKeyguard(any(), any(), any());
.startPendingIntentDismissingKeyguard(any(), any(), anyView());
}
@Test
@@ -746,7 +751,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
verify(mActivityStarter, times(1))
.startPendingIntentDismissingKeyguard(any(), any(), any());
.startPendingIntentDismissingKeyguard(any(), any(), anyView());
}
@Test