From 8ef4cf5bbb92f99e80de5ea3fdf8cc641b77db7a Mon Sep 17 00:00:00 2001 From: Jordan Demeulenaere Date: Fri, 23 Apr 2021 11:22:06 +0200 Subject: [PATCH] Polish status bar launch animations. This CL improves the ongoing call animation by: 1. Making sure that the status bar window matches its parent size during the animation, so that the expanding view is not clipped. 2. Expanding the view below the other status bar text. See b/183229367#comment8 and #comment11 for before/after videos. Bug: 183229367 Test: Create an ongoing call notification, tap it Change-Id: Idfd8a43109584a4b21ad0a3e5f5e2028a87a9c60 --- .../animation/ActivityLaunchAnimator.kt | 31 ++++++----- .../DelegateLaunchAnimatorController.kt | 10 ++++ .../GhostedViewLaunchAnimatorController.kt | 22 ++++---- .../SystemUI/res/layout/super_status_bar.xml | 5 ++ .../NotificationLaunchAnimatorController.kt | 8 ++- .../systemui/statusbar/phone/StatusBar.java | 52 ++++++++++++++++--- .../phone/StatusBarWindowController.java | 38 ++++++++++++-- .../animation/ActivityLaunchAnimatorTest.kt | 11 ++-- 8 files changed, 132 insertions(+), 45 deletions(-) create mode 100644 packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt index 20273d05f0597..1cf0c5f52bb49 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt @@ -17,6 +17,7 @@ import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget import android.view.SyncRtSurfaceTransactionApplier import android.view.View +import android.view.ViewGroup import android.view.WindowManager import android.view.animation.AnimationUtils import android.view.animation.PathInterpolator @@ -112,7 +113,7 @@ class ActivityLaunchAnimator(context: Context) { @PublishedApi internal fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) { if (Looper.myLooper() != Looper.getMainLooper()) { - this.getRootView().context.mainExecutor.execute { + this.launchContainer.context.mainExecutor.execute { this.onIntentStarted(willAnimate) } } else { @@ -166,15 +167,19 @@ class ActivityLaunchAnimator(context: Context) { } /** - * Return the root [View] that contains the view that started the intent and will be - * animating together with the window. + * The container in which the view that started the intent will be animating together with + * the opening window. * - * This view will be used to: + * This will be used to: * - Get the associated [Context]. * - Compute whether we are expanding fully above the current window. * - Apply surface transactions in sync with RenderThread. + * + * This container can be changed to force this [Controller] to animate the expanding view + * inside a different location, for instance to ensure correct layering during the + * animation. */ - fun getRootView(): View + var launchContainer: ViewGroup /** * Return the [State] of the view that will be animated. We will animate from this state to @@ -272,9 +277,9 @@ class ActivityLaunchAnimator(context: Context) { @VisibleForTesting inner 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 val launchContainer = controller.launchContainer + @PublishedApi internal val context = launchContainer.context + private val transactionApplier = SyncRtSurfaceTransactionApplier(launchContainer) private var animator: ValueAnimator? = null private var windowCrop = Rect() @@ -291,11 +296,11 @@ class ActivityLaunchAnimator(context: Context) { @PublishedApi internal fun postTimeout() { - rootView.postDelayed(onTimeout, LAUNCH_TIMEOUT) + launchContainer.postDelayed(onTimeout, LAUNCH_TIMEOUT) } private fun removeTimeout() { - rootView.removeCallbacks(onTimeout) + launchContainer.removeCallbacks(onTimeout) } override fun onAnimationStart( @@ -369,11 +374,11 @@ class ActivityLaunchAnimator(context: Context) { val endWidth = endRight - endLeft // TODO(b/184121838): Ensure that we are launching on the same screen. - val rootViewLocation = rootView.locationOnScreen + val rootViewLocation = launchContainer.locationOnScreen val isExpandingFullyAbove = endTop <= rootViewLocation[1] && - endBottom >= rootViewLocation[1] + rootView.height && + endBottom >= rootViewLocation[1] + launchContainer.height && endLeft <= rootViewLocation[0] && - endRight >= rootViewLocation[0] + rootView.width + endRight >= rootViewLocation[0] + launchContainer.width // TODO(b/184121838): We should somehow get the top and bottom radius of the window. val endRadius = if (isExpandingFullyAbove) { diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt new file mode 100644 index 0000000000000..d4be25382395a --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt @@ -0,0 +1,10 @@ +package com.android.systemui.animation + +/** + * A base class to easily create an implementation of [ActivityLaunchAnimator.Controller] which + * delegates most of its call to [delegate]. This is mostly useful for Java code which can't easily + * create such a delegated class. + */ +open class DelegateLaunchAnimatorController( + protected val delegate: ActivityLaunchAnimator.Controller +) : ActivityLaunchAnimator.Controller by delegate \ No newline at end of file diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index 3da45210e8c2b..ce9feede87592 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -14,6 +14,7 @@ import android.graphics.drawable.LayerDrawable import android.view.GhostView import android.view.View import android.view.ViewGroup +import android.view.ViewGroupOverlay import android.widget.FrameLayout import kotlin.math.min @@ -32,9 +33,10 @@ 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 container to which we will add the ghost view and expanding background. */ + override var launchContainer = ghostedView.rootView as ViewGroup + private val launchContainerOverlay: ViewGroupOverlay + get() = launchContainer.overlay /** The ghost view that is drawn and animated instead of the ghosted view. */ private var ghostView: GhostView? = null @@ -42,7 +44,7 @@ open class GhostedViewLaunchAnimatorController( private val ghostViewMatrix = Matrix() /** - * The expanding background view that will be added to [rootView] (below [ghostView]) and + * The expanding background view that will be added to [launchContainer] (below [ghostView]) and * animate. */ private var backgroundView: FrameLayout? = null @@ -96,10 +98,6 @@ open class GhostedViewLaunchAnimatorController( return gradient.cornerRadii?.get(CORNER_RADIUS_BOTTOM_INDEX) ?: gradient.cornerRadius } - override fun getRootView(): View { - return rootView - } - override fun createAnimatorState(): ActivityLaunchAnimator.State { val location = ghostedView.locationOnScreen return ActivityLaunchAnimator.State( @@ -113,10 +111,10 @@ open class GhostedViewLaunchAnimatorController( } override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) { - backgroundView = FrameLayout(rootView.context).apply { + backgroundView = FrameLayout(launchContainer.context).apply { forceHasOverlappingRendering(false) } - rootViewOverlay.add(backgroundView) + launchContainerOverlay.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. @@ -127,7 +125,7 @@ open class GhostedViewLaunchAnimatorController( // 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 { + ghostView = GhostView.addGhost(ghostedView, launchContainer).apply { setLayerType(View.LAYER_TYPE_HARDWARE, null) } @@ -169,7 +167,7 @@ open class GhostedViewLaunchAnimatorController( backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha GhostView.removeGhost(ghostedView) - rootViewOverlay.remove(backgroundView) + launchContainerOverlay.remove(backgroundView) ghostedView.invalidate() } diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml index 7142929bab644..5176d966a6949 100644 --- a/packages/SystemUI/res/layout/super_status_bar.xml +++ b/packages/SystemUI/res/layout/super_status_bar.xml @@ -25,6 +25,11 @@ android:layout_height="match_parent" android:fitsSystemWindows="true"> + + { ActivityOptions options = new ActivityOptions( getActivityOptions(mDisplayId, adapter)); @@ -2858,6 +2854,46 @@ public class StatusBar extends SystemUI implements DemoMode, afterKeyguardGone, true /* deferred */); } + @Nullable + private ActivityLaunchAnimator.Controller wrapAnimationController( + @Nullable ActivityLaunchAnimator.Controller animationController, boolean dismissShade) { + if (animationController == null) { + return null; + } + + View rootView = animationController.getLaunchContainer().getRootView(); + if (rootView == mSuperStatusBarViewFactory.getStatusBarWindowView()) { + // We are animating a view in the status bar. We have to make sure that the status bar + // window matches the full screen during the animation and that we are expanding the + // view below the other status bar text. + animationController.setLaunchContainer( + mStatusBarWindowController.getLaunchAnimationContainer()); + + return new DelegateLaunchAnimatorController(animationController) { + @Override + public void onLaunchAnimationStart(boolean isExpandingFullyAbove) { + getDelegate().onLaunchAnimationStart(isExpandingFullyAbove); + mStatusBarWindowController.setLaunchAnimationRunning(true); + } + + @Override + public void onLaunchAnimationEnd(boolean isExpandingFullyAbove) { + getDelegate().onLaunchAnimationEnd(isExpandingFullyAbove); + mStatusBarWindowController.setLaunchAnimationRunning(false); + } + }; + } + + if (dismissShade && rootView == mNotificationShadeWindowView) { + // We are animating a view in the shade. We have to make sure that we collapse it when + // the animation ends or is cancelled. + return new StatusBarLaunchAnimatorController(animationController, this, + true /* isLaunchForActivity */); + } + + return animationController; + } + public void readyForKeyguardDone() { mStatusBarKeyguardViewManager.readyForKeyguardDone(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java index 2f7278b38d15b..30b8c5c0d8d34 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowController.java @@ -29,6 +29,7 @@ import android.view.Gravity; import android.view.ViewGroup; import android.view.WindowManager; +import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.SuperStatusBarViewFactory; @@ -51,6 +52,7 @@ public class StatusBarWindowController { private final State mCurrentState = new State(); private ViewGroup mStatusBarView; + private ViewGroup mLaunchAnimationContainer; private WindowManager.LayoutParams mLp; private final WindowManager.LayoutParams mLpChanged; @@ -62,6 +64,8 @@ public class StatusBarWindowController { mWindowManager = windowManager; mSuperStatusBarViewFactory = superStatusBarViewFactory; mStatusBarView = mSuperStatusBarViewFactory.getStatusBarWindowView(); + mLaunchAnimationContainer = mStatusBarView.findViewById( + R.id.status_bar_launch_animation_container); mLpChanged = new WindowManager.LayoutParams(); mResources = resources; @@ -124,13 +128,38 @@ public class StatusBarWindowController { apply(mCurrentState); } - private void applyHeight() { - mLpChanged.height = mBarHeight; + /** + * Return the container in which we should run launch animations started from the status bar and + * expanding into the opening window. + * + * @see #setLaunchAnimationRunning + */ + public ViewGroup getLaunchAnimationContainer() { + return mLaunchAnimationContainer; + } + + /** + * Set whether a launch animation is currently running. If true, this will ensure that the + * window matches its parent height so that the animation is not clipped by the normal status + * bar height. + */ + public void setLaunchAnimationRunning(boolean isLaunchAnimationRunning) { + if (isLaunchAnimationRunning == mCurrentState.mIsLaunchAnimationRunning) { + return; + } + + mCurrentState.mIsLaunchAnimationRunning = isLaunchAnimationRunning; + apply(mCurrentState); + } + + private void applyHeight(State state) { + mLpChanged.height = + state.mIsLaunchAnimationRunning ? ViewGroup.LayoutParams.MATCH_PARENT : mBarHeight; } private void apply(State state) { applyForceStatusBarVisibleFlag(state); - applyHeight(); + applyHeight(state); if (mLp != null && mLp.copyFrom(mLpChanged) != 0) { mWindowManager.updateViewLayout(mStatusBarView, mLp); } @@ -138,10 +167,11 @@ public class StatusBarWindowController { private static class State { boolean mForceStatusBarVisible; + boolean mIsLaunchAnimationRunning; } private void applyForceStatusBarVisibleFlag(State state) { - if (state.mForceStatusBarVisible) { + if (state.mForceStatusBarVisible || state.mIsLaunchAnimationRunning) { mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; } else { mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt index fbba09a255e73..897d78b2ba0e5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityLaunchAnimatorTest.kt @@ -11,7 +11,8 @@ import android.view.IRemoteAnimationFinishedCallback import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget import android.view.SurfaceControl -import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import junit.framework.Assert.assertFalse @@ -36,8 +37,8 @@ import kotlin.concurrent.thread @RunWithLooper class ActivityLaunchAnimatorTest : SysuiTestCase() { private val activityLaunchAnimator = ActivityLaunchAnimator(mContext) - private val rootView = View(mContext) - @Spy private val controller = TestLaunchAnimatorController(rootView) + private val launchContainer = LinearLayout(mContext) + @Spy private val controller = TestLaunchAnimatorController(launchContainer) @Mock lateinit var iCallback: IRemoteAnimationFinishedCallback @get:Rule val rule = MockitoJUnit.rule() @@ -146,10 +147,8 @@ class ActivityLaunchAnimatorTest : SysuiTestCase() { * outside of the main thread. */ private class TestLaunchAnimatorController( - private val rootView: View + override var launchContainer: ViewGroup ) : ActivityLaunchAnimator.Controller { - override fun getRootView(): View = rootView - override fun createAnimatorState() = ActivityLaunchAnimator.State( top = 100, bottom = 200,