From a867af2d8747bdef741b26e99a19812fdb75cdf9 Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Thu, 1 Apr 2021 11:36:41 +0200 Subject: [PATCH] Extract ActivityLaunchAnimator into a reusable class. This CL removes all logic specific to notifications in ActivityLaunchAnimator, so that it can be reused for other launch animations. I would suggest to look at ag/14057092 in parallel to make more sense of the abstractions added in this CL. For the sake of not making this CL even bigger than it already is, a few things still need to be done in follow-up CLs: - Show the status bar icons at the right time, instead of at the end of the animation, when using the StatusBarLaunchAnimatorController. - Move the animation/ package outside of the SystemUIPluginLib library and instead have it in its own reusable library. - Replace the animation durations and interpolator to the latest designs. - Improve split screen by retrieving the final window bounds and prevent the clipping of the window during the animation. - Handle animations in the lock screen. For review, I would recommend to review in order: 1. ActivityLaunchAnimator.kt 2. StatusBarLaunchAnimatorController.kt 3. NotificationLaunchAnimatorController.kt 4. GhostedViewLaunchAnimatorController.kt 5. Everything else. Bug: 184121838 Bug: 181654098 Test: Tap a notification when the shade is open and unlocked. Change-Id: If4c3c64fcd153bb8e89111f56332013ca6dff156 --- packages/SystemUI/plugin/Android.bp | 1 + .../systemui/plugins/ActivityStarter.java | 19 +- .../animation/ActivityLaunchAnimator.kt | 479 +++++++++++++++++ .../GhostedViewLaunchAnimatorController.kt | 196 +++++++ .../systemui/ActivityStarterDelegate.java | 25 +- .../systemui/statusbar/CrossFadeHelper.java | 4 +- .../NotificationShadeDepthController.kt | 4 +- .../NotificationShadeWindowController.java | 7 +- .../notification/ActivityLaunchAnimator.java | 490 ------------------ .../notification/ExpandAnimationParameters.kt | 44 ++ .../NotificationLaunchAnimatorController.kt | 136 +++++ .../row/ExpandableNotificationRow.java | 9 +- .../row/NotificationBackgroundView.java | 10 +- .../stack/NotificationListContainer.java | 3 +- .../stack/NotificationStackScrollLayout.java | 2 +- ...tificationStackScrollLayoutController.java | 5 +- .../NotificationPanelViewController.java | 11 +- ...NotificationShadeWindowControllerImpl.java | 5 + ...NotificationShadeWindowViewController.java | 16 +- .../systemui/statusbar/phone/StatusBar.java | 117 ++++- .../StatusBarLaunchAnimatorController.kt | 47 ++ .../StatusBarNotificationActivityStarter.java | 133 ++--- .../phone/StatusBarNotificationPresenter.java | 10 +- .../util/animation/TransitionLayout.kt | 18 + .../animation/ActivityLaunchAnimatorTest.kt | 188 +++++++ .../NotificationShadeDepthControllerTest.kt | 8 +- .../ActivityLaunchAnimatorTest.java | 132 ----- ...tusBarNotificationActivityStarterTest.java | 28 +- .../StatusBarNotificationPresenterTest.java | 3 +- .../statusbar/policy/SmartReplyViewTest.java | 13 +- 30 files changed, 1382 insertions(+), 781 deletions(-) create mode 100644 packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt create mode 100644 packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java diff --git a/packages/SystemUI/plugin/Android.bp b/packages/SystemUI/plugin/Android.bp index d6204db978726..b3aba223c7d72 100644 --- a/packages/SystemUI/plugin/Android.bp +++ b/packages/SystemUI/plugin/Android.bp @@ -27,6 +27,7 @@ java_library { srcs: [ "src/**/*.java", + "src/**/*.kt", "bcsmartspace/src/**/*.java", ], diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 25a3fa2f5f20b..e003b2e1d68ac 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -19,6 +19,7 @@ import android.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, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt new file mode 100644 index 0000000000000..e8bdb6755f97d --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/ActivityLaunchAnimator.kt @@ -0,0 +1,479 @@ +package com.android.systemui.plugins.animation + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.animation.ValueAnimator +import android.app.ActivityManager +import android.app.PendingIntent +import android.graphics.Matrix +import android.graphics.Rect +import android.os.RemoteException +import android.util.MathUtils +import android.view.IRemoteAnimationFinishedCallback +import android.view.IRemoteAnimationRunner +import android.view.RemoteAnimationAdapter +import android.view.RemoteAnimationTarget +import android.view.SyncRtSurfaceTransactionApplier +import android.view.View +import android.view.WindowManager +import android.view.animation.LinearInterpolator +import android.view.animation.PathInterpolator +import com.android.internal.annotations.VisibleForTesting +import com.android.internal.policy.ScreenDecorationsUtils +import kotlin.math.roundToInt + +/** + * A class that allows activities to be started in a seamless way from a view that is transforming + * nicely into the starting window. + */ +class ActivityLaunchAnimator { + companion object { + const val ANIMATION_DURATION = 400L + const val ANIMATION_DURATION_FADE_OUT_CONTENT = 67L + const val ANIMATION_DURATION_FADE_IN_WINDOW = 200L + private const val ANIMATION_DURATION_NAV_FADE_IN = 266L + private const val ANIMATION_DURATION_NAV_FADE_OUT = 133L + private const val ANIMATION_DELAY_NAV_FADE_IN = + ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN + private const val LAUNCH_TIMEOUT = 500L + + // TODO(b/184121838): Use android.R.interpolator.fast_out_extra_slow_in instead. + // TODO(b/184121838): Move com.android.systemui.Interpolators in an animation library we can + // reuse here. + private val ANIMATION_INTERPOLATOR = PathInterpolator(0f, 0f, 0.2f, 1f) + private val LINEAR_INTERPOLATOR = LinearInterpolator() + private val ALPHA_IN_INTERPOLATOR = PathInterpolator(0.4f, 0f, 1f, 1f) + private val ALPHA_OUT_INTERPOLATOR = PathInterpolator(0f, 0f, 0.8f, 1f) + private val NAV_FADE_IN_INTERPOLATOR = PathInterpolator(0f, 0f, 0f, 1f) + private val NAV_FADE_OUT_INTERPOLATOR = PathInterpolator(0.2f, 0f, 1f, 1f) + + /** + * Given the [linearProgress] of a launch animation, return the linear progress of the + * sub-animation starting [delay] ms after the launch animation and that lasts [duration]. + */ + @JvmStatic + fun getProgress(linearProgress: Float, delay: Long, duration: Long): Float { + return MathUtils.constrain( + (linearProgress * ANIMATION_DURATION - delay) / duration, + 0.0f, + 1.0f + ) + } + } + + /** + * Start an intent and animate the opening window. The intent will be started by running + * [intentStarter], which should use the provided [RemoteAnimationAdapter] and return the launch + * result. [controller] is responsible from animating the view from which the intent was started + * in [Controller.onLaunchAnimationProgress]. No animation will start if there is no window + * opening. + * + * If [controller] is null, then the intent will be started and no animation will run. + * + * This method will throw any exception thrown by [intentStarter]. + */ + inline fun startIntentWithAnimation( + controller: Controller?, + intentStarter: (RemoteAnimationAdapter?) -> Int + ) { + if (controller == null) { + intentStarter(null) + return + } + + val runner = Runner(controller) + val animationAdapter = RemoteAnimationAdapter( + runner, + ANIMATION_DURATION, + ANIMATION_DURATION - 150 /* statusBarTransitionDelay */ + ) + val launchResult = intentStarter(animationAdapter) + val willAnimate = launchResult == ActivityManager.START_TASK_TO_FRONT || + launchResult == ActivityManager.START_SUCCESS + runner.context.mainExecutor.execute { controller.onIntentStarted(willAnimate) } + + // If we expect an animation, post a timeout to cancel it in case the remote animation is + // never started. + if (willAnimate) { + runner.postTimeout() + } + } + + /** + * Same as [startIntentWithAnimation] but allows [intentStarter] to throw a + * [PendingIntent.CanceledException] which must then be handled by the caller. This is useful + * for Java caller starting a [PendingIntent]. + */ + @Throws(PendingIntent.CanceledException::class) + fun startPendingIntentWithAnimation( + controller: Controller?, + intentStarter: PendingIntentStarter + ) { + startIntentWithAnimation(controller) { intentStarter.startPendingIntent(it) } + } + + interface PendingIntentStarter { + /** + * Start a pending intent using the provided [animationAdapter] and return the launch + * result. + */ + @Throws(PendingIntent.CanceledException::class) + fun startPendingIntent(animationAdapter: RemoteAnimationAdapter?): Int + } + + /** + * A controller that takes care of applying the animation to an expanding view. + * + * Note that all callbacks (onXXX methods) are all called on the main thread. + */ + interface Controller { + companion object { + /** + * Return a [Controller] that will animate and expand [view] into the opening window. + * + * Important: The view must be attached to the window when calling this function and + * during the animation. + */ + @JvmStatic + fun fromView(view: View): Controller = GhostedViewLaunchAnimatorController(view) + } + + /** + * Return the root [View] that contains the view that started the intent and will be + * animating together with the window. + * + * This view will be used to: + * - Get the associated [Context]. + * - Compute whether we are expanding fully above the current window. + * - Apply surface transactions in sync with RenderThread. + */ + fun getRootView(): View + + /** + * Return the [State] of the view that will be animated. We will animate from this state to + * the final window state. + * + * Note: This state will be mutated and passed to [onLaunchAnimationProgress] during the + * animation. + */ + fun createAnimatorState(): State + + /** + * The intent was started. If [willAnimate] is false, nothing else will happen and the + * animation will not be started. + */ + fun onIntentStarted(willAnimate: Boolean) {} + + /** + * The animation started. This is typically used to initialize any additional resource + * needed for the animation. [isExpandingFullyAbove] will be true if the window is expanding + * fully above the [root view][getRootView]. + */ + fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {} + + /** The animation made progress and the expandable view [state] should be updated. */ + fun onLaunchAnimationProgress(state: State, progress: Float, linearProgress: Float) {} + + /** + * The animation ended. This will be called *if and only if* [onLaunchAnimationStart] was + * called previously. This is typically used to clean up the resources initialized when the + * animation was started. + */ + fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {} + + /** + * The animation was cancelled remotely. Note that [onLaunchAnimationEnd] will still be + * called after this if the animation was already started, i.e. if [onLaunchAnimationStart] + * was called before the cancellation. + */ + fun onLaunchAnimationCancelled() {} + + /** + * The remote animation was not started within the expected time. It timed out and will + * never [start][onLaunchAnimationStart]. + */ + fun onLaunchAnimationTimedOut() {} + + /** + * The animation was aborted because the opening window was not found. It will never + * [start][onLaunchAnimationStart]. + */ + fun onLaunchAnimationAborted() {} + } + + /** The state of an expandable view during an [ActivityLaunchAnimator] animation. */ + open class State( + /** The position of the view in screen space coordinates. */ + var top: Int, + var bottom: Int, + var left: Int, + var right: Int, + + var topCornerRadius: Float = 0f, + var bottomCornerRadius: Float = 0f, + + var contentAlpha: Float = 1f, + var backgroundAlpha: Float = 1f + ) { + private val startTop = top + private val startLeft = left + private val startRight = right + + val width: Int + get() = right - left + + val height: Int + get() = bottom - top + + open val topChange: Int + get() = top - startTop + + val leftChange: Int + get() = left - startLeft + + val rightChange: Int + get() = right - startRight + } + + @VisibleForTesting + class Runner(private val controller: Controller) : IRemoteAnimationRunner.Stub() { + private val rootView = controller.getRootView() + @PublishedApi internal val context = rootView.context + private val transactionApplier = SyncRtSurfaceTransactionApplier(rootView) + private var animator: ValueAnimator? = null + + private var windowCrop = Rect() + private var timedOut = false + private var cancelled = false + + // A timeout to cancel the remote animation if it is not started within X milliseconds after + // the intent was started. + // + // Note that this is important to keep this a Runnable (and not a Kotlin lambda), otherwise + // it will be automatically converted when posted and we wouldn't be able to remove it after + // posting it. + private var onTimeout = Runnable { onAnimationTimedOut() } + + @PublishedApi + internal fun postTimeout() { + rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT) + } + + private fun removeTimeout() { + rootView.removeCallbacks(onTimeout) + } + + override fun onAnimationStart( + @WindowManager.TransitionOldType transit: Int, + remoteAnimationTargets: Array, + remoteAnimationWallpaperTargets: Array, + remoteAnimationNonAppTargets: Array, + 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, + iCallback: IRemoteAnimationFinishedCallback + ) { + val window = remoteAnimationTargets.firstOrNull { + it.mode == RemoteAnimationTarget.MODE_OPENING + } + + if (window == null) { + removeTimeout() + invokeCallback(iCallback) + controller.onLaunchAnimationAborted() + return + } + + val navigationBar = remoteAnimationTargets.firstOrNull { + it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR + } + + // Start state. + val state = controller.createAnimatorState() + + val startTop = state.top + val startBottom = state.bottom + val startLeft = state.left + val startRight = state.right + + val startTopCornerRadius = state.topCornerRadius + val startBottomCornerRadius = state.bottomCornerRadius + + // End state. + val windowBounds = window.screenSpaceBounds + val endTop = windowBounds.top + val endBottom = windowBounds.bottom + val endLeft = windowBounds.left + val endRight = windowBounds.right + + // TODO(b/184121838): Ensure that we are launching on the same screen. + val rootViewLocation = rootView.locationOnScreen + val isExpandingFullyAbove = endTop <= rootViewLocation[1] && + endBottom >= rootViewLocation[1] + rootView.height && + endLeft <= rootViewLocation[0] && + endRight >= rootViewLocation[0] + rootView.width + + // TODO(b/184121838): We should somehow get the top and bottom radius of the window. + val endRadius = if (isExpandingFullyAbove) { + // Most of the time, expanding fully above the root view means expanding in full + // screen. + ScreenDecorationsUtils.getWindowCornerRadius(context.resources) + } else { + // This usually means we are in split screen mode, so 2 out of 4 corners will have + // a radius of 0. + 0f + } + + // Update state. + val animator = ValueAnimator.ofFloat(0f, 1f) + this.animator = animator + animator.duration = ANIMATION_DURATION + animator.interpolator = LINEAR_INTERPOLATOR + + animator.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationStart(animation: Animator?, isReverse: Boolean) { + controller.onLaunchAnimationStart(isExpandingFullyAbove) + } + + override fun onAnimationEnd(animation: Animator?) { + invokeCallback(iCallback) + controller.onLaunchAnimationEnd(isExpandingFullyAbove) + } + }) + + animator.addUpdateListener { animation -> + if (cancelled) { + return@addUpdateListener + } + + val linearProgress = animation.animatedFraction + val progress = ANIMATION_INTERPOLATOR.getInterpolation(linearProgress) + + state.top = lerp(startTop, endTop, progress).roundToInt() + state.bottom = lerp(startBottom, endBottom, progress).roundToInt() + state.left = lerp(startLeft, endLeft, progress).roundToInt() + state.right = lerp(startRight, endRight, progress).roundToInt() + + state.topCornerRadius = MathUtils.lerp(startTopCornerRadius, endRadius, progress) + state.bottomCornerRadius = + MathUtils.lerp(startBottomCornerRadius, endRadius, progress) + + val contentAlphaProgress = getProgress(linearProgress, 0, + ANIMATION_DURATION_FADE_OUT_CONTENT) + state.contentAlpha = + 1 - ALPHA_OUT_INTERPOLATOR.getInterpolation(contentAlphaProgress) + + val backgroundAlphaProgress = getProgress(linearProgress, + ANIMATION_DURATION_FADE_OUT_CONTENT, ANIMATION_DURATION_FADE_IN_WINDOW) + state.backgroundAlpha = + 1 - ALPHA_IN_INTERPOLATOR.getInterpolation(backgroundAlphaProgress) + + applyStateToWindow(window, state) + navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) } + controller.onLaunchAnimationProgress(state, progress, linearProgress) + } + + animator.start() + } + + private fun applyStateToWindow(window: RemoteAnimationTarget, state: State) { + val m = Matrix() + m.postTranslate(0f, (state.top - window.sourceContainerBounds.top).toFloat()) + windowCrop.set(state.left, 0, state.right, state.height) + + val cornerRadius = minOf(state.topCornerRadius, state.bottomCornerRadius) + val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash) + .withAlpha(1f) + .withMatrix(m) + .withWindowCrop(windowCrop) + .withLayer(window.prefixOrderIndex) + .withCornerRadius(cornerRadius) + .withVisibility(true) + .build() + + transactionApplier.scheduleApply(params) + } + + private fun applyStateToNavigationBar( + navigationBar: RemoteAnimationTarget, + state: State, + linearProgress: Float + ) { + val fadeInProgress = getProgress(linearProgress, ANIMATION_DELAY_NAV_FADE_IN, + ANIMATION_DURATION_NAV_FADE_OUT) + + val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash) + if (fadeInProgress > 0) { + val m = Matrix() + m.postTranslate(0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat()) + windowCrop.set(state.left, 0, state.right, state.height) + params + .withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress)) + .withMatrix(m) + .withWindowCrop(windowCrop) + .withVisibility(true) + } else { + val fadeOutProgress = getProgress(linearProgress, 0, + ANIMATION_DURATION_NAV_FADE_OUT) + params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)) + } + + transactionApplier.scheduleApply(params.build()) + } + + private fun onAnimationTimedOut() { + if (cancelled) { + return + } + + timedOut = true + controller.onLaunchAnimationTimedOut() + } + + override fun onAnimationCancelled() { + if (timedOut) { + return + } + + cancelled = true + removeTimeout() + context.mainExecutor.execute { + animator?.cancel() + controller.onLaunchAnimationCancelled() + } + } + + private fun invokeCallback(iCallback: IRemoteAnimationFinishedCallback) { + try { + iCallback.onAnimationFinished() + } catch (e: RemoteException) { + e.printStackTrace() + } + } + + private fun lerp(start: Int, stop: Int, amount: Float): Float { + return MathUtils.lerp(start.toFloat(), stop.toFloat(), amount) + } + } +} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt new file mode 100644 index 0000000000000..a23722433a4db --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/animation/GhostedViewLaunchAnimatorController.kt @@ -0,0 +1,196 @@ +package com.android.systemui.plugins.animation + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.PixelFormat +import android.graphics.PorterDuff +import android.graphics.PorterDuffXfermode +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.view.GhostView +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout + +/** + * A base implementation of [ActivityLaunchAnimator.Controller] which creates a [ghost][GhostView] + * of [ghostedView] as well as an expandable background view, which are drawn and animated instead + * of the ghosted view. + * + * Important: [ghostedView] must be attached to the window when calling this function and during the + * animation. + * + * Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView] + * whenever possible instead. + */ +open class GhostedViewLaunchAnimatorController( + /** The view that will be ghosted and from which the background will be extracted. */ + private val ghostedView: View +) : ActivityLaunchAnimator.Controller { + /** The root view to which we will add the ghost view and expanding background. */ + private val rootView = ghostedView.rootView as ViewGroup + private val rootViewOverlay = rootView.overlay + + /** The ghost view that is drawn and animated instead of the ghosted view. */ + private var ghostView: View? = null + + /** + * The expanding background view that will be added to [rootView] (below [ghostView]) and + * animate. + */ + private var backgroundView: FrameLayout? = null + + /** + * The drawable wrapping the [ghostedView] background and used as background for + * [backgroundView]. + */ + private var backgroundDrawable: WrappedDrawable? = null + private var startBackgroundAlpha: Int = 0xFF + + /** + * Return the background of the [ghostedView]. This background will be used to draw the + * background of the background view that is expanding up to the final animation position. This + * is called at the start of the animation. + * + * Note that during the animation, the alpha value value of this background will be set to 0, + * then set back to its initial value at the end of the animation. + */ + protected open fun getBackground(): Drawable? = ghostedView.background + + /** + * Set the corner radius of [background]. The background is the one that was returned by + * [getBackground]. + */ + protected open fun setBackgroundCornerRadius( + background: Drawable, + topCornerRadius: Float, + bottomCornerRadius: Float + ) { + // TODO(b/184121838): Add default support for GradientDrawable and LayerDrawable to make + // this work out of the box for common rounded backgrounds. + } + + /** Return the current top corner radius of the background. */ + protected open fun getCurrentTopCornerRadius(): Float = 0f + + /** Return the current bottom corner radius of the background. */ + protected open fun getCurrentBottomCornerRadius(): Float = 0f + + override fun getRootView(): View { + return rootView + } + + override fun createAnimatorState(): ActivityLaunchAnimator.State { + val location = ghostedView.locationOnScreen + return ActivityLaunchAnimator.State( + top = location[1], + bottom = location[1] + ghostedView.height, + left = location[0], + right = location[0] + ghostedView.width, + topCornerRadius = getCurrentTopCornerRadius(), + bottomCornerRadius = getCurrentBottomCornerRadius() + ) + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + backgroundView = FrameLayout(rootView.context).apply { + forceHasOverlappingRendering(true) + } + rootViewOverlay.add(backgroundView) + + // We wrap the ghosted view background and use it to draw the expandable background. Its + // alpha will be set to 0 as soon as we start drawing the expanding background. + val drawable = getBackground() + startBackgroundAlpha = drawable?.alpha ?: 0xFF + backgroundDrawable = WrappedDrawable(drawable) + backgroundView?.background = backgroundDrawable + + // Create a ghost of the view that will be moving and fading out. This allows to fade out + // the content before fading out the background. + ghostView = GhostView.addGhost(ghostedView, rootView).apply { + setLayerType(View.LAYER_TYPE_HARDWARE, null) + } + } + + override fun onLaunchAnimationProgress( + state: ActivityLaunchAnimator.State, + progress: Float, + linearProgress: Float + ) { + val ghostView = this.ghostView!! + ghostView.translationX = (state.leftChange + state.rightChange) / 2.toFloat() + ghostView.translationY = state.topChange.toFloat() + ghostView.alpha = state.contentAlpha + + val backgroundView = this.backgroundView!! + backgroundView.top = state.top + backgroundView.bottom = state.bottom + backgroundView.left = state.left + backgroundView.right = state.right + + val backgroundDrawable = backgroundDrawable!! + backgroundDrawable.alpha = (0xFF * state.backgroundAlpha).toInt() + backgroundDrawable.wrapped?.let { + setBackgroundCornerRadius(it, state.topCornerRadius, state.bottomCornerRadius) + } + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha + + GhostView.removeGhost(ghostedView) + rootViewOverlay.remove(backgroundView) + ghostedView.invalidate() + } + + private class WrappedDrawable(val wrapped: Drawable?) : Drawable() { + companion object { + private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC) + } + + private var currentAlpha = 0xFF + private var previousBounds = Rect() + + override fun draw(canvas: Canvas) { + val wrapped = this.wrapped ?: return + + wrapped.copyBounds(previousBounds) + + wrapped.alpha = currentAlpha + wrapped.bounds = bounds + wrapped.setXfermode(SRC_MODE) + + wrapped.draw(canvas) + + // The background view (and therefore this drawable) is drawn before the ghost view, so + // the ghosted view background alpha should always be 0 when it is drawn above the + // background. + wrapped.alpha = 0 + wrapped.bounds = previousBounds + wrapped.setXfermode(null) + } + + override fun setAlpha(alpha: Int) { + if (alpha != currentAlpha) { + currentAlpha = alpha + invalidateSelf() + } + } + + override fun getAlpha() = currentAlpha + + override fun getOpacity(): Int { + val wrapped = this.wrapped ?: return PixelFormat.TRANSPARENT + + val previousAlpha = wrapped.alpha + wrapped.alpha = currentAlpha + val opacity = wrapped.opacity + wrapped.alpha = previousAlpha + return opacity + } + + override fun setColorFilter(filter: ColorFilter?) { + wrapped?.colorFilter = filter + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 3d6d38149fc54..ecbb70eb0f039 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -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( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java index 8e6398f3630bf..952597556faf2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CrossFadeHelper.java @@ -105,9 +105,7 @@ 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); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt index e27c1a21b8cbe..a0a4e3106f96b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt @@ -35,7 +35,7 @@ import com.android.systemui.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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 24515f7bc210d..737167e8ed0cd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -90,9 +90,14 @@ 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) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java deleted file mode 100644 index 23d13d33bda0f..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; - -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.app.ActivityManager; -import android.graphics.Matrix; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.MathUtils; -import android.view.IRemoteAnimationFinishedCallback; -import android.view.IRemoteAnimationRunner; -import android.view.RemoteAnimationAdapter; -import android.view.RemoteAnimationTarget; -import android.view.SyncRtSurfaceTransactionApplier; -import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; -import android.view.View; -import android.view.WindowManager; -import android.view.animation.Interpolator; -import android.view.animation.PathInterpolator; - -import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.systemui.Interpolators; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController; - -import java.util.concurrent.Executor; - -/** - * A class that allows activities to be launched in a seamless way where the notification - * transforms nicely into the starting window. - */ -public class ActivityLaunchAnimator { - - private static final int ANIMATION_DURATION = 400; - public static final long ANIMATION_DURATION_FADE_CONTENT = 67; - public static final long ANIMATION_DURATION_FADE_APP = 200; - public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION - - CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY - - 16; - private static final int ANIMATION_DURATION_NAV_FADE_IN = 266; - private static final int ANIMATION_DURATION_NAV_FADE_OUT = 133; - private static final long ANIMATION_DELAY_NAV_FADE_IN = - ANIMATION_DURATION - ANIMATION_DURATION_NAV_FADE_IN; - private static final Interpolator NAV_FADE_IN_INTERPOLATOR = - new PathInterpolator(0f, 0f, 0f, 1f); - private static final Interpolator NAV_FADE_OUT_INTERPOLATOR = - new PathInterpolator(0.2f, 0f, 1f, 1f); - private static final long LAUNCH_TIMEOUT = 500; - private final NotificationPanelViewController mNotificationPanel; - private final NotificationListContainer mNotificationContainer; - private final float mWindowCornerRadius; - private final NotificationShadeWindowViewController mNotificationShadeWindowViewController; - private final NotificationShadeDepthController mDepthController; - private final Executor mMainExecutor; - private Callback mCallback; - private final Runnable mTimeoutRunnable = () -> { - setAnimationPending(false); - mCallback.onExpandAnimationTimedOut(); - }; - private boolean mAnimationPending; - private boolean mAnimationRunning; - private boolean mIsLaunchForActivity; - - public ActivityLaunchAnimator( - NotificationShadeWindowViewController notificationShadeWindowViewController, - Callback callback, - NotificationPanelViewController notificationPanel, - NotificationShadeDepthController depthController, - NotificationListContainer container, - Executor mainExecutor) { - mNotificationPanel = notificationPanel; - mNotificationContainer = container; - mDepthController = depthController; - mNotificationShadeWindowViewController = notificationShadeWindowViewController; - mCallback = callback; - mMainExecutor = mainExecutor; - mWindowCornerRadius = ScreenDecorationsUtils - .getWindowCornerRadius(mNotificationShadeWindowViewController.getView() - .getResources()); - } - - public RemoteAnimationAdapter getLaunchAnimation( - View sourceView, boolean occluded) { - if (!(sourceView instanceof ExpandableNotificationRow) - || !mCallback.areLaunchAnimationsEnabled() || occluded) { - return null; - } - AnimationRunner animationRunner = new AnimationRunner( - (ExpandableNotificationRow) sourceView); - return new RemoteAnimationAdapter(animationRunner, ANIMATION_DURATION, - ANIMATION_DURATION - 150 /* statusBarTransitionDelay */); - } - - public boolean isAnimationPending() { - return mAnimationPending; - } - - /** - * Set the launch result the intent requested - * - * @param launchResult the launch result - * @param wasIntentActivity was this launch for an activity - */ - public void setLaunchResult(int launchResult, boolean wasIntentActivity) { - mIsLaunchForActivity = wasIntentActivity; - setAnimationPending((launchResult == ActivityManager.START_TASK_TO_FRONT - || launchResult == ActivityManager.START_SUCCESS) - && mCallback.areLaunchAnimationsEnabled()); - } - - public boolean isLaunchForActivity() { - return mIsLaunchForActivity; - } - - private void setAnimationPending(boolean pending) { - mAnimationPending = pending; - mNotificationShadeWindowViewController.setExpandAnimationPending(pending); - if (pending) { - mNotificationShadeWindowViewController.getView().postDelayed(mTimeoutRunnable, - LAUNCH_TIMEOUT); - } else { - mNotificationShadeWindowViewController.getView().removeCallbacks(mTimeoutRunnable); - } - } - - public boolean isAnimationRunning() { - return mAnimationRunning; - } - - class AnimationRunner extends IRemoteAnimationRunner.Stub { - - private final ExpandableNotificationRow mSourceNotification; - private final ExpandAnimationParameters mParams; - private final Rect mWindowCrop = new Rect(); - private boolean mIsFullScreenLaunch = true; - private final SyncRtSurfaceTransactionApplier mSyncRtTransactionApplier; - - private final float mNotificationStartTopCornerRadius; - private final float mNotificationStartBottomCornerRadius; - - AnimationRunner(ExpandableNotificationRow sourceNotification) { - mSourceNotification = sourceNotification; - mParams = new ExpandAnimationParameters(); - mSyncRtTransactionApplier = new SyncRtSurfaceTransactionApplier(mSourceNotification); - mNotificationStartTopCornerRadius = mSourceNotification.getCurrentBackgroundRadiusTop(); - mNotificationStartBottomCornerRadius = - mSourceNotification.getCurrentBackgroundRadiusBottom(); - } - - @Override - public void onAnimationStart(@WindowManager.TransitionOldType int transit, - RemoteAnimationTarget[] remoteAnimationTargets, - RemoteAnimationTarget[] remoteAnimationWallpaperTargets, - RemoteAnimationTarget[] remoteAnimationNonAppTargets, - IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback) - throws RemoteException { - mMainExecutor.execute(() -> { - RemoteAnimationTarget primary = getPrimaryRemoteAnimationTarget( - remoteAnimationTargets); - if (primary == null) { - setAnimationPending(false); - invokeCallback(iRemoteAnimationFinishedCallback); - mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */); - return; - } - - setExpandAnimationRunning(true); - mIsFullScreenLaunch = primary.position.y == 0 - && primary.sourceContainerBounds.height() - >= mNotificationPanel.getHeight(); - if (!mIsFullScreenLaunch) { - mNotificationPanel.collapseWithDuration(ANIMATION_DURATION); - } - ValueAnimator anim = ValueAnimator.ofFloat(0, 1); - mParams.startPosition = mSourceNotification.getLocationOnScreen(); - mParams.startTranslationZ = mSourceNotification.getTranslationZ(); - mParams.startClipTopAmount = mSourceNotification.getClipTopAmount(); - if (mSourceNotification.isChildInGroup()) { - int parentClip = mSourceNotification - .getNotificationParent().getClipTopAmount(); - mParams.parentStartClipTopAmount = parentClip; - // We need to calculate how much the child is clipped by the parent - // because children always have 0 clipTopAmount - if (parentClip != 0) { - float childClip = parentClip - - mSourceNotification.getTranslationY(); - if (childClip > 0.0f) { - mParams.startClipTopAmount = (int) Math.ceil(childClip); - } - } - } - int targetWidth = primary.sourceContainerBounds.width(); - // If the notification panel is collapsed, the clip may be larger than the height. - int notificationHeight = Math.max(mSourceNotification.getActualHeight() - - mSourceNotification.getClipBottomAmount(), 0); - int notificationWidth = mSourceNotification.getWidth(); - final RemoteAnimationTarget navigationBarTarget = - getNavBarRemoteAnimationTarget(remoteAnimationNonAppTargets); - anim.setDuration(ANIMATION_DURATION); - anim.setInterpolator(Interpolators.LINEAR); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animation) { - mParams.linearProgress = animation.getAnimatedFraction(); - float progress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( - mParams.linearProgress); - int newWidth = (int) MathUtils.lerp(notificationWidth, - targetWidth, progress); - mParams.left = (int) ((targetWidth - newWidth) / 2.0f); - mParams.right = mParams.left + newWidth; - mParams.top = (int) MathUtils.lerp(mParams.startPosition[1], - primary.position.y, progress); - mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1] - + notificationHeight, - primary.position.y + primary.sourceContainerBounds.bottom, - progress); - mParams.topCornerRadius = MathUtils.lerp(mNotificationStartTopCornerRadius, - mWindowCornerRadius, progress); - mParams.bottomCornerRadius = MathUtils.lerp( - mNotificationStartBottomCornerRadius, - mWindowCornerRadius, progress); - applyParamsToWindow(primary); - applyParamsToNotification(mParams); - applyParamsToNotificationShade(mParams); - applyNavigationBarParamsToWindow(navigationBarTarget); - } - }); - anim.addListener(new AnimatorListenerAdapter() { - private boolean mWasCancelled; - - @Override - public void onAnimationStart(Animator animation) { - InteractionJankMonitor.getInstance().begin(mSourceNotification, - CUJ_NOTIFICATION_APP_START); - } - - @Override - public void onAnimationCancel(Animator animation) { - mWasCancelled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - setExpandAnimationRunning(false); - invokeCallback(iRemoteAnimationFinishedCallback); - if (!mWasCancelled) { - InteractionJankMonitor.getInstance().end(CUJ_NOTIFICATION_APP_START); - } else { - InteractionJankMonitor.getInstance().cancel(CUJ_NOTIFICATION_APP_START); - } - } - }); - anim.start(); - setAnimationPending(false); - }); - } - - private void invokeCallback(IRemoteAnimationFinishedCallback callback) { - try { - callback.onAnimationFinished(); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - - private RemoteAnimationTarget getPrimaryRemoteAnimationTarget( - RemoteAnimationTarget[] remoteAnimationTargets) { - RemoteAnimationTarget primary = null; - for (RemoteAnimationTarget app : remoteAnimationTargets) { - if (app.mode == RemoteAnimationTarget.MODE_OPENING) { - primary = app; - break; - } - } - return primary; - } - - private RemoteAnimationTarget getNavBarRemoteAnimationTarget( - RemoteAnimationTarget[] remoteAnimationTargets) { - RemoteAnimationTarget navBar = null; - for (RemoteAnimationTarget target : remoteAnimationTargets) { - if (target.windowType == TYPE_NAVIGATION_BAR) { - navBar = target; - break; - } - } - return navBar; - } - - private void setExpandAnimationRunning(boolean running) { - mNotificationPanel.setLaunchingNotification(running); - mSourceNotification.setExpandAnimationRunning(running); - mNotificationShadeWindowViewController.setExpandAnimationRunning(running); - mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null); - mAnimationRunning = running; - if (!running) { - mCallback.onExpandAnimationFinished(mIsFullScreenLaunch); - applyParamsToNotification(null); - applyParamsToNotificationShade(null); - } - - } - - private void applyParamsToNotificationShade(ExpandAnimationParameters params) { - mNotificationContainer.applyExpandAnimationParams(params); - mNotificationPanel.applyExpandAnimationParams(params); - mDepthController.setNotificationLaunchAnimationParams(params); - } - - private void applyParamsToNotification(ExpandAnimationParameters params) { - mSourceNotification.applyExpandAnimationParams(params); - } - - private void applyParamsToWindow(RemoteAnimationTarget app) { - Matrix m = new Matrix(); - m.postTranslate(0, (float) (mParams.top - app.position.y)); - mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); - float cornerRadius = Math.min(mParams.topCornerRadius, mParams.bottomCornerRadius); - SurfaceParams params = new SurfaceParams.Builder(app.leash) - .withAlpha(1f) - .withMatrix(m) - .withWindowCrop(mWindowCrop) - .withLayer(app.prefixOrderIndex) - .withCornerRadius(cornerRadius) - .withVisibility(true) - .build(); - mSyncRtTransactionApplier.scheduleApply(params); - } - - private void applyNavigationBarParamsToWindow(RemoteAnimationTarget navBarTarget) { - if (navBarTarget == null) { - return; - } - - // calculate navigation bar fade-out progress - final float fadeOutProgress = mParams.getProgress(0, - ANIMATION_DURATION_NAV_FADE_OUT); - - // calculate navigation bar fade-in progress - final float fadeInProgress = mParams.getProgress(ANIMATION_DELAY_NAV_FADE_IN, - ANIMATION_DURATION_NAV_FADE_OUT); - - final SurfaceParams.Builder builder = new SurfaceParams.Builder(navBarTarget.leash); - if (fadeInProgress > 0) { - Matrix m = new Matrix(); - m.postTranslate(0, (float) (mParams.top - navBarTarget.position.y)); - mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight()); - builder.withMatrix(m) - .withWindowCrop(mWindowCrop) - .withVisibility(true); - builder.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress)); - } else { - builder.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress)); - } - mSyncRtTransactionApplier.scheduleApply(builder.build()); - } - - @Override - public void onAnimationCancelled() throws RemoteException { - mMainExecutor.execute(() -> { - setAnimationPending(false); - mCallback.onLaunchAnimationCancelled(); - }); - } - }; - - public static class ExpandAnimationParameters { - public float linearProgress; - int[] startPosition; - float startTranslationZ; - int left; - int top; - int right; - int bottom; - int startClipTopAmount; - int parentStartClipTopAmount; - float topCornerRadius; - float bottomCornerRadius; - - public ExpandAnimationParameters() { - } - - public int getTop() { - return top; - } - - public int getBottom() { - return bottom; - } - - public int getWidth() { - return right - left; - } - - public int getHeight() { - return bottom - top; - } - - public int getTopChange() { - // We need this compensation to ensure that the QS moves in sync. - int clipTopAmountCompensation = 0; - if (startClipTopAmount != 0.0f) { - clipTopAmountCompensation = (int) MathUtils.lerp(0, startClipTopAmount, - Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress)); - } - return Math.min(top - startPosition[1] - clipTopAmountCompensation, 0); - } - - public float getProgress() { - return linearProgress; - } - - public float getProgress(long delay, long duration) { - return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay) - / duration, 0.0f, 1.0f); - } - - public int getStartClipTopAmount() { - return startClipTopAmount; - } - - public int getParentStartClipTopAmount() { - return parentStartClipTopAmount; - } - - public float getStartTranslationZ() { - return startTranslationZ; - } - - public float getTopCornerRadius() { - return topCornerRadius; - } - - public float getBottomCornerRadius() { - return bottomCornerRadius; - } - } - - public interface Callback { - - /** - * Called when the launch animation was cancelled. - */ - void onLaunchAnimationCancelled(); - - /** - * Called when the launch animation has timed out without starting an actual animation. - */ - void onExpandAnimationTimedOut(); - - /** - * Called when the expand animation has finished. - * - * @param launchIsFullScreen True if this launch was fullscreen, such that now the window - * fills the whole screen - */ - void onExpandAnimationFinished(boolean launchIsFullScreen); - - /** - * Are animations currently enabled. - */ - boolean areLaunchAnimationsEnabled(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt new file mode 100644 index 0000000000000..d5835fc6c50e2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ExpandAnimationParameters.kt @@ -0,0 +1,44 @@ +package com.android.systemui.statusbar.notification + +import android.util.MathUtils +import com.android.internal.annotations.VisibleForTesting +import com.android.systemui.Interpolators +import com.android.systemui.plugins.animation.ActivityLaunchAnimator +import kotlin.math.min + +/** Parameters for the notifications expand animations. */ +class ExpandAnimationParameters( + top: Int, + bottom: Int, + left: Int, + right: Int, + + topCornerRadius: Float = 0f, + bottomCornerRadius: Float = 0f +) : ActivityLaunchAnimator.State(top, bottom, left, right, topCornerRadius, bottomCornerRadius) { + @VisibleForTesting + constructor() : this( + top = 0, bottom = 0, left = 0, right = 0, topCornerRadius = 0f, bottomCornerRadius = 0f + ) + + var startTranslationZ = 0f + var startClipTopAmount = 0 + var parentStartClipTopAmount = 0 + var progress = 0f + var linearProgress = 0f + + override val topChange: Int + get() { + // We need this compensation to ensure that the QS moves in sync. + var clipTopAmountCompensation = 0 + if (startClipTopAmount.toFloat() != 0.0f) { + clipTopAmountCompensation = MathUtils.lerp(0f, startClipTopAmount.toFloat(), + Interpolators.FAST_OUT_SLOW_IN.getInterpolation(linearProgress)).toInt() + } + return min(super.topChange - clipTopAmountCompensation, 0) + } + + fun getProgress(delay: Long, duration: Long): Float { + return ActivityLaunchAnimator.getProgress(linearProgress, delay, duration) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt new file mode 100644 index 0000000000000..2f966b4e6d784 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt @@ -0,0 +1,136 @@ +package com.android.systemui.statusbar.notification + +import android.view.View +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.statusbar.NotificationShadeDepthController +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.stack.NotificationListContainer +import com.android.systemui.statusbar.phone.NotificationPanelViewController +import com.android.systemui.plugins.animation.ActivityLaunchAnimator +import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController +import kotlin.math.ceil +import kotlin.math.max + +/** A provider of [NotificationLaunchAnimatorController]. */ +class NotificationLaunchAnimatorControllerProvider( + private val notificationShadeWindowViewController: NotificationShadeWindowViewController, + private val notificationPanelViewController: NotificationPanelViewController, + private val notificationListContainer: NotificationListContainer, + private val depthController: NotificationShadeDepthController +) { + fun getAnimatorController( + notification: ExpandableNotificationRow + ): NotificationLaunchAnimatorController { + return NotificationLaunchAnimatorController( + notificationShadeWindowViewController, + notificationPanelViewController, + notificationListContainer, + depthController, + notification + ) + } +} + +/** + * An [ActivityLaunchAnimator.Controller] that animates an [ExpandableNotificationRow]. An instance + * of this class can be passed to [ActivityLaunchAnimator.startIntentWithAnimation] to animate a + * notification expanding into an opening window. + */ +class NotificationLaunchAnimatorController( + private val notificationShadeWindowViewController: NotificationShadeWindowViewController, + private val notificationPanelViewController: NotificationPanelViewController, + private val notificationListContainer: NotificationListContainer, + private val depthController: NotificationShadeDepthController, + private val notification: ExpandableNotificationRow +) : ActivityLaunchAnimator.Controller { + override fun getRootView(): View = notification.rootView + + override fun createAnimatorState(): ActivityLaunchAnimator.State { + // If the notification panel is collapsed, the clip may be larger than the height. + val height = max(0, notification.actualHeight - notification.clipBottomAmount) + val location = notification.locationOnScreen + + val params = ExpandAnimationParameters( + top = location[1], + bottom = location[1] + height, + left = location[0], + right = location[0] + notification.width, + topCornerRadius = notification.currentBackgroundRadiusTop, + bottomCornerRadius = notification.currentBackgroundRadiusBottom + ) + + params.startTranslationZ = notification.translationZ + params.startClipTopAmount = notification.clipTopAmount + if (notification.isChildInGroup) { + val parentClip = notification.notificationParent.clipTopAmount + params.parentStartClipTopAmount = parentClip + + // We need to calculate how much the child is clipped by the parent because children + // always have 0 clipTopAmount + if (parentClip != 0) { + val childClip = parentClip - notification.translationY + if (childClip > 0) { + params.startClipTopAmount = ceil(childClip.toDouble()).toInt() + } + } + } + + return params + } + + override fun onIntentStarted(willAnimate: Boolean) { + notificationShadeWindowViewController.setExpandAnimationRunning(willAnimate) + } + + override fun onLaunchAnimationCancelled() { + // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started + // here? + notificationShadeWindowViewController.setExpandAnimationRunning(false) + } + + override fun onLaunchAnimationTimedOut() { + notificationShadeWindowViewController.setExpandAnimationRunning(false) + } + + override fun onLaunchAnimationAborted() { + notificationShadeWindowViewController.setExpandAnimationRunning(false) + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + notificationPanelViewController.setLaunchingNotification(true) + notification.isExpandAnimationRunning = true + notificationListContainer.setExpandingNotification(notification) + + InteractionJankMonitor.getInstance().begin(notification, + InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + InteractionJankMonitor.getInstance().end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START) + + notificationPanelViewController.setLaunchingNotification(false) + notification.isExpandAnimationRunning = false + notificationShadeWindowViewController.setExpandAnimationRunning(false) + notificationListContainer.setExpandingNotification(null) + applyParams(null) + } + + private fun applyParams(params: ExpandAnimationParameters?) { + notification.applyExpandAnimationParams(params) + notificationListContainer.applyExpandAnimationParams(params) + notificationPanelViewController.applyExpandAnimationParams(params) + depthController.notificationLaunchAnimationParams = params + } + + override fun onLaunchAnimationProgress( + state: ActivityLaunchAnimator.State, + progress: Float, + linearProgress: Float + ) { + val params = state as ExpandAnimationParameters + params.progress = progress + params.linearProgress = linearProgress + + applyParams(params) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a3a40144806ec..207a89482b175 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -19,7 +19,6 @@ 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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java index 95885633a3e88..07d1e68aef807 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationBackgroundView.java @@ -31,7 +31,8 @@ import 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(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java index 72f32161038a0..2a2e733f78a18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationListContainer.java @@ -16,14 +16,13 @@ package com.android.systemui.statusbar.notification.stack; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; - import android.view.View; import android.view.ViewGroup; import androidx.annotation.Nullable; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; +import com.android.systemui.statusbar.notification.ExpandAnimationParameters; import com.android.systemui.statusbar.notification.NotificationActivityStarter; import com.android.systemui.statusbar.notification.VisibilityLocationProvider; import com.android.systemui.statusbar.notification.collection.NotificationEntry; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 40c0b895400eb..ad06e7d06270b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -17,7 +17,6 @@ package com.android.systemui.statusbar.notification.stack; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; -import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters; import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManagerKt.BUCKET_SILENT; import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE; import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT; @@ -91,6 +90,7 @@ import com.android.systemui.statusbar.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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 7baad1cc265ed..ce7b3979c52d8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -75,8 +75,8 @@ import com.android.systemui.statusbar.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); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 555df5c37ee31..0cad113979139 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -26,7 +26,6 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ 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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index d074e64d337ea..ee78c004c168f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java index 2ff7c9933edfc..72f7ff87ed2d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java @@ -92,7 +92,6 @@ 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); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 8ed9cd66aed1e..5fd0d6602a948 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -173,6 +173,7 @@ import com.android.systemui.plugins.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 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) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt new file mode 100644 index 0000000000000..d45f64fd83b08 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt @@ -0,0 +1,47 @@ +package com.android.systemui.statusbar.phone + +import com.android.systemui.plugins.animation.ActivityLaunchAnimator + +/** + * A [ActivityLaunchAnimator.Controller] that takes care of collapsing the status bar at the right + * time. + */ +class StatusBarLaunchAnimatorController( + private val delegate: ActivityLaunchAnimator.Controller, + private val statusBar: StatusBar, + private val isLaunchForActivity: Boolean = true +) : ActivityLaunchAnimator.Controller by delegate { + override fun onIntentStarted(willAnimate: Boolean) { + delegate.onIntentStarted(willAnimate) + if (!willAnimate) { + statusBar.collapsePanelOnMainThread() + } + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + delegate.onLaunchAnimationStart(isExpandingFullyAbove) + if (!isExpandingFullyAbove) { + statusBar.collapsePanelWithDuration(ActivityLaunchAnimator.ANIMATION_DURATION.toInt()) + } + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + delegate.onLaunchAnimationEnd(isExpandingFullyAbove) + statusBar.onLaunchAnimationEnd(isExpandingFullyAbove) + } + + override fun onLaunchAnimationCancelled() { + delegate.onLaunchAnimationCancelled() + statusBar.onLaunchAnimationCancelled() + } + + override fun onLaunchAnimationTimedOut() { + delegate.onLaunchAnimationTimedOut() + statusBar.onLaunchAnimationTimedOut(isLaunchForActivity) + } + + override fun onLaunchAnimationAborted() { + delegate.onLaunchAnimationAborted() + statusBar.collapsePanelOnMainThread() + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java index 801ac964777b0..2e918da0d5605 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java @@ -21,7 +21,6 @@ import static 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); } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java index 94edd1e0415a9..088f9475c7d8c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java @@ -57,7 +57,6 @@ import com.android.systemui.statusbar.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() { diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt index 603d423143ce1..46611e03e588f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt @@ -50,6 +50,8 @@ 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() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt new file mode 100644 index 0000000000000..722b0b175b762 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/plugins/animation/ActivityLaunchAnimatorTest.kt @@ -0,0 +1,188 @@ +package com.android.systemui.plugins.animation + +import android.app.ActivityManager +import android.app.WindowConfiguration +import android.graphics.Point +import android.graphics.Rect +import android.os.Looper +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.IRemoteAnimationFinishedCallback +import android.view.RemoteAnimationAdapter +import android.view.RemoteAnimationTarget +import android.view.SurfaceControl +import android.view.View +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import junit.framework.Assert.assertFalse +import junit.framework.Assert.assertNotNull +import junit.framework.Assert.assertNull +import junit.framework.Assert.assertTrue +import junit.framework.AssertionFailedError +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import kotlin.concurrent.thread + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +class ActivityLaunchAnimatorTest : SysuiTestCase() { + private val activityLaunchAnimator = ActivityLaunchAnimator() + private val rootView = View(mContext) + @Spy private val controller = TestLaunchAnimatorController(rootView) + @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback + + @get:Rule val rule = MockitoJUnit.rule() + + private fun startIntentWithAnimation( + controller: ActivityLaunchAnimator.Controller? = this.controller, + intentStarter: (RemoteAnimationAdapter?) -> Int + ) { + // We start in a new thread so that we can ensure that the callbacks are called in the main + // thread. + thread { + activityLaunchAnimator.startIntentWithAnimation(controller, intentStarter) + }.join() + } + + @Test + fun animationAdapterIsNullIfControllerIsNull() { + var startedIntent = false + var animationAdapter: RemoteAnimationAdapter? = null + + startIntentWithAnimation(controller = null) { adapter -> + startedIntent = true + animationAdapter = adapter + + ActivityManager.START_SUCCESS + } + + assertTrue(startedIntent) + assertNull(animationAdapter) + } + + @Test + fun animatesIfActivityOpens() { + val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) + var animationAdapter: RemoteAnimationAdapter? = null + startIntentWithAnimation { adapter -> + animationAdapter = adapter + ActivityManager.START_SUCCESS + } + + assertNotNull(animationAdapter) + waitForIdleSync() + verify(controller).onIntentStarted(willAnimateCaptor.capture()) + assertTrue(willAnimateCaptor.value) + } + + @Test + fun doesNotAnimateIfActivityIsAlreadyOpen() { + val willAnimateCaptor = ArgumentCaptor.forClass(Boolean::class.java) + startIntentWithAnimation { ActivityManager.START_DELIVERED_TO_TOP } + + waitForIdleSync() + verify(controller).onIntentStarted(willAnimateCaptor.capture()) + assertFalse(willAnimateCaptor.value) + } + + @Test + fun doesNotStartIfAnimationIsCancelled() { + val runner = ActivityLaunchAnimator.Runner(controller) + runner.onAnimationCancelled() + runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) + + waitForIdleSync() + verify(controller).onLaunchAnimationCancelled() + verify(controller, never()).onLaunchAnimationStart(anyBoolean()) + } + + @Test + fun abortsIfNoOpeningWindowIsFound() { + val runner = ActivityLaunchAnimator.Runner(controller) + runner.onAnimationStart(0, emptyArray(), emptyArray(), emptyArray(), iCallback) + + waitForIdleSync() + verify(controller).onLaunchAnimationAborted() + verify(controller, never()).onLaunchAnimationStart(anyBoolean()) + } + + @Test + fun startsAnimationIfWindowIsOpening() { + val runner = ActivityLaunchAnimator.Runner(controller) + runner.onAnimationStart(0, arrayOf(fakeWindow()), emptyArray(), emptyArray(), iCallback) + waitForIdleSync() + verify(controller).onLaunchAnimationStart(anyBoolean()) + } + + private fun fakeWindow() = RemoteAnimationTarget( + 0, RemoteAnimationTarget.MODE_OPENING, SurfaceControl(), false, Rect(), Rect(), 0, + Point(), Rect(), Rect(), WindowConfiguration(), false, SurfaceControl(), Rect(), + ActivityManager.RunningTaskInfo() + ) +} + +/** + * A simple implementation of [ActivityLaunchAnimator.Controller] which throws if it is called + * outside of the main thread. + */ +private class TestLaunchAnimatorController( + private val rootView: View +) : ActivityLaunchAnimator.Controller { + override fun getRootView(): View = rootView + + override fun createAnimatorState() = ActivityLaunchAnimator.State( + top = 100, + bottom = 200, + left = 300, + right = 400, + topCornerRadius = 10f, + bottomCornerRadius = 20f + ) + + private fun assertOnMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw AssertionFailedError("Called outside of main thread.") + } + } + + override fun onIntentStarted(willAnimate: Boolean) { + assertOnMainThread() + } + + override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { + assertOnMainThread() + } + + override fun onLaunchAnimationProgress( + state: ActivityLaunchAnimator.State, + progress: Float, + linearProgress: Float + ) { + assertOnMainThread() + } + + override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) { + assertOnMainThread() + } + + override fun onLaunchAnimationCancelled() { + assertOnMainThread() + } + + override fun onLaunchAnimationTimedOut() { + assertOnMainThread() + } + + override fun onLaunchAnimationAborted() { + assertOnMainThread() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt index e65db5ea41923..fd94812980696 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt @@ -27,7 +27,7 @@ import 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) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java deleted file mode 100644 index 2fa6cf02d8b46..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.notification; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.app.ActivityManager; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.RemoteAnimationAdapter; -import android.view.View; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.NotificationShadeDepthController; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; -import com.android.systemui.statusbar.notification.stack.NotificationListContainer; -import com.android.systemui.statusbar.phone.NotificationPanelViewController; -import com.android.systemui.statusbar.phone.NotificationShadeWindowView; -import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnit; -import org.mockito.junit.MockitoRule; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -public class ActivityLaunchAnimatorTest extends SysuiTestCase { - - private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); - private ActivityLaunchAnimator mLaunchAnimator; - @Mock - private ActivityLaunchAnimator.Callback mCallback; - @Mock - private NotificationShadeWindowViewController mNotificationShadeWindowViewController; - @Mock - private NotificationShadeWindowView mNotificationShadeWindowView; - @Mock - private NotificationListContainer mNotificationContainer; - @Mock - private ExpandableNotificationRow mRow; - @Mock - private NotificationShadeDepthController mNotificationShadeDepthController; - @Mock - private NotificationPanelViewController mNotificationPanelViewController; - @Rule - public MockitoRule rule = MockitoJUnit.rule(); - - @Before - public void setUp() throws Exception { - when(mNotificationShadeWindowViewController.getView()) - .thenReturn(mNotificationShadeWindowView); - when(mNotificationShadeWindowView.getResources()).thenReturn(mContext.getResources()); - when(mCallback.areLaunchAnimationsEnabled()).thenReturn(true); - mLaunchAnimator = new ActivityLaunchAnimator( - mNotificationShadeWindowViewController, - mCallback, - mNotificationPanelViewController, - mNotificationShadeDepthController, - mNotificationContainer, - mExecutor); - } - - @Test - public void testReturnsNullIfNotEnabled() { - when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false); - RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, - false /* occluded */); - Assert.assertTrue("The LaunchAnimator generated an animation even though animations are " - + "disabled", launchAnimation == null); - } - - @Test - public void testNotWorkingWhenOccluded() { - when(mCallback.areLaunchAnimationsEnabled()).thenReturn(false); - RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, - true /* occluded */); - Assert.assertTrue("The LaunchAnimator generated an animation even though we're occluded", - launchAnimation == null); - } - - @Test - public void testTimeoutCalled() { - RemoteAnimationAdapter launchAnimation = mLaunchAnimator.getLaunchAnimation(mRow, - false /* occluded */); - Assert.assertTrue("No animation generated", launchAnimation != null); - executePostsImmediately(mNotificationShadeWindowView); - mLaunchAnimator.setLaunchResult(ActivityManager.START_SUCCESS, - true /* wasIntentActivity */); - verify(mCallback).onExpandAnimationTimedOut(); - } - - private void executePostsImmediately(View view) { - doAnswer((i) -> { - Runnable run = i.getArgument(0); - run.run(); - return null; - }).when(view).post(any()); - doAnswer((i) -> { - Runnable run = i.getArgument(0); - run.run(); - return null; - }).when(view).postDelayed(any(), anyLong()); - } -} - diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java index 68464cefb3f6b..e34bc0c4a8ec9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java @@ -35,7 +35,6 @@ import 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(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index c0ebfadf6b572..8601de5611d58 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -47,7 +47,6 @@ import com.android.systemui.statusbar.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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 5de62b908c64e..3d07eb16ba588 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -703,6 +703,10 @@ 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