Merge "Extract ActivityLaunchAnimator into a reusable class." into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
ccd86069fc
@@ -27,6 +27,7 @@ java_library {
|
||||
|
||||
srcs: [
|
||||
"src/**/*.java",
|
||||
"src/**/*.kt",
|
||||
"bcsmartspace/src/**/*.java",
|
||||
],
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user