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,