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,