Add return animation functionality to TransitionAnimator.
Bug: 323863002
Flag: ACONFIG com.android.systemui.shared.return_animation_framework_library DISABLED
Test: Manually tested existing use cases with the flag on and off (same
behavior), added unit test
Change-Id: I391c6ae784871e17262649d0f17343ac87b6a6d4
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f911269..5d1e2c3 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -233,6 +233,9 @@
resource_dirs: [
"tests/res",
],
+ asset_dirs: [
+ "tests/goldens",
+ ],
static_libs: [
"SystemUI-res",
"WifiTrackerLib",
@@ -334,6 +337,8 @@
"androidx.test.ext.junit",
"androidx.test.ext.truth",
"kotlin-test",
+ "platform-screenshot-diff-core",
+ "PlatformMotionTesting",
"SystemUICustomizationTestUtils",
"androidx.compose.runtime_runtime",
],
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 2268d16..dce2c65 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -48,6 +48,7 @@
"SystemUIShaderLib",
"WindowManager-Shell-shared",
"animationlib",
+ "com_android_systemui_shared_flags_lib",
],
manifest: "AndroidManifest.xml",
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index ea1cb34..9ce30fd 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -790,7 +790,7 @@
controller,
endState,
windowBackgroundColor,
- fadeOutWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
+ fadeWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
drawHole = !controller.isBelowAnimatingWindow,
)
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index dbdf970..20087c4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -917,6 +917,12 @@
endController.transitionContainer = value
}
+ // We tell TransitionController that this is always a launch, and handle the launch
+ // vs return logic internally.
+ // TODO(b/323863002): maybe move the launch vs return logic out of this class and
+ // delegate it to TransitionController?
+ override val isLaunching: Boolean = true
+
override fun createAnimatorState(): TransitionAnimator.State {
return startController.createAnimatorState()
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
index 3f57f88..9ad0fc5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewTransitionAnimatorController.kt
@@ -64,6 +64,7 @@
private var interactionJankMonitor: InteractionJankMonitor =
InteractionJankMonitor.getInstance(),
) : ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
/** The container to which we will add the ghost view and expanding background. */
override var transitionContainer = ghostedView.rootView as ViewGroup
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 5e4276c..9bf6b34 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -28,7 +28,9 @@
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
+import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators.LINEAR
+import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import kotlin.math.roundToInt
private const val TAG = "TransitionAnimator"
@@ -70,13 +72,14 @@
interface Controller {
/**
* The container in which the view that started the animation will be animating together
- * with the opening window.
+ * with the opening or closing window.
*
* This will be used to:
* - Get the associated [Context].
- * - Compute whether we are expanding fully above the transition container.
- * - Get to overlay to which we initially put the window background layer, until the opening
- * window is made visible (see [openingWindowSyncView]).
+ * - Compute whether we are expanding to or contracting from fully above the transition
+ * container.
+ * - Get the overlay into which we put the window background layer, while the animating
+ * window is not visible (see [openingWindowSyncView]).
*
* 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
@@ -84,12 +87,17 @@
*/
var transitionContainer: ViewGroup
+ /** Whether the animation being controlled is a launch or a return. */
+ val isLaunching: Boolean
+
/**
- * The [View] with which the opening app window should be synchronized with once it starts
- * to be visible.
+ * If [isLaunching], the [View] with which the opening app window should be synchronized
+ * once it starts to be visible. Otherwise, the [View] with which the closing app window
+ * should be synchronized until it stops being visible.
*
* We will also move the window background layer to this view's overlay once the opening
- * window is visible.
+ * window is visible (if [isLaunching]), or from this view's overlay once the closing window
+ * stop being visible (if ![isLaunching]).
*
* If null, this will default to [transitionContainer].
*/
@@ -203,17 +211,56 @@
* layer with [windowBackgroundColor] will fade in then (optionally) fade out above the
* expanding view, and should be the same background color as the opening (or closing) window.
*
- * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
- * the second half of the animation, and will have SRC blending mode (ultimately punching a hole
- * in the [transition container][Controller.transitionContainer]) iff [drawHole] is true.
+ * If [fadeWindowBackgroundLayer] is true, then this intermediary layer will fade out during the
+ * second half of the animation (if [Controller.isLaunching] or fade in during the first half of
+ * the animation (if ![Controller.isLaunching]), and will have SRC blending mode (ultimately
+ * punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole]
+ * is true.
*/
fun startAnimation(
controller: Controller,
endState: State,
windowBackgroundColor: Int,
- fadeOutWindowBackgroundLayer: Boolean = true,
+ fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
): Animation {
+ if (!controller.isLaunching) checkReturnAnimationFrameworkFlag()
+
+ // We add an extra layer with the same color as the dialog/app splash screen background
+ // color, which is usually the same color of the app background. We first fade in this layer
+ // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
+ // transition container and reveal the opening window.
+ val windowBackgroundLayer =
+ GradientDrawable().apply {
+ setColor(windowBackgroundColor)
+ alpha = 0
+ }
+
+ val animator =
+ createAnimator(
+ controller,
+ endState,
+ windowBackgroundLayer,
+ fadeWindowBackgroundLayer,
+ drawHole
+ )
+ animator.start()
+
+ return object : Animation {
+ override fun cancel() {
+ animator.cancel()
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun createAnimator(
+ controller: Controller,
+ endState: State,
+ windowBackgroundLayer: GradientDrawable,
+ fadeWindowBackgroundLayer: Boolean = true,
+ drawHole: Boolean = false
+ ): ValueAnimator {
val state = controller.createAnimatorState()
// Start state.
@@ -255,31 +302,24 @@
val transitionContainer = controller.transitionContainer
val isExpandingFullyAbove = isExpandingFullyAbove(transitionContainer, endState)
- // We add an extra layer with the same color as the dialog/app splash screen background
- // color, which is usually the same color of the app background. We first fade in this layer
- // to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
- // transition container and reveal the opening window.
- val windowBackgroundLayer =
- GradientDrawable().apply {
- setColor(windowBackgroundColor)
- alpha = 0
- }
-
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = timings.totalDuration
animator.interpolator = LINEAR
// Whether we should move the [windowBackgroundLayer] into the overlay of
- // [Controller.openingWindowSyncView] once the opening app window starts to be visible.
+ // [Controller.openingWindowSyncView] once the opening app window starts to be visible, or
+ // from it once the closing app window stops being visible.
+ // This is necessary as a one-off sync so we can avoid syncing at every frame, especially
+ // in complex interactions like launching an activity from a dialog. See
+ // b/214961273#comment2 for more details.
val openingWindowSyncView = controller.openingWindowSyncView
val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
- val moveBackgroundLayerWhenAppIsVisible =
+ val moveBackgroundLayerWhenAppVisibilityChanges =
openingWindowSyncView != null &&
openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
val transitionContainerOverlay = transitionContainer.overlay
- var cancelled = false
var movedBackgroundLayer = false
animator.addListener(
@@ -293,7 +333,11 @@
// Add the drawable to the transition container overlay. Overlays always draw
// drawables after views, so we know that it will be drawn above any view added
// by the controller.
- transitionContainerOverlay.add(windowBackgroundLayer)
+ if (controller.isLaunching || openingWindowSyncViewOverlay == null) {
+ transitionContainerOverlay.add(windowBackgroundLayer)
+ } else {
+ openingWindowSyncViewOverlay.add(windowBackgroundLayer)
+ }
}
override fun onAnimationEnd(animation: Animator) {
@@ -303,7 +347,7 @@
controller.onTransitionAnimationEnd(isExpandingFullyAbove)
transitionContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppIsVisible) {
+ if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) {
openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
}
}
@@ -311,12 +355,6 @@
)
animator.addUpdateListener { animation ->
- if (cancelled) {
- // TODO(b/184121838): Cancel the animator directly instead of just skipping the
- // update.
- return@addUpdateListener
- }
-
maybeUpdateEndState()
// TODO(b/184121838): Use reverse interpolators to get the same path/arc as the non
@@ -338,20 +376,34 @@
state.bottomCornerRadius =
MathUtils.lerp(startBottomCornerRadius, endBottomCornerRadius, progress)
- // The expanding view can/should be hidden once it is completely covered by the opening
- // window.
state.visible =
- getProgress(
- timings,
- linearProgress,
- timings.contentBeforeFadeOutDelay,
- timings.contentBeforeFadeOutDuration
- ) < 1
+ if (controller.isLaunching) {
+ // The expanding view can/should be hidden once it is completely covered by the
+ // opening window.
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ ) < 1
+ } else {
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ ) > 0
+ }
- if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) {
- // The expanding view is not visible, so the opening app is visible. If this is the
- // first frame when it happens, trigger a one-off sync and move the background layer
- // in its new container.
+ if (
+ controller.isLaunching &&
+ moveBackgroundLayerWhenAppVisibilityChanges &&
+ !state.visible &&
+ !movedBackgroundLayer
+ ) {
+ // The expanding view is not visible, so the opening app is visible. If this is
+ // the first frame when it happens, trigger a one-off sync and move the
+ // background layer in its new container.
movedBackgroundLayer = true
transitionContainerOverlay.remove(windowBackgroundLayer)
@@ -362,6 +414,25 @@
openingWindowSyncView,
then = {}
)
+ } else if (
+ !controller.isLaunching &&
+ moveBackgroundLayerWhenAppVisibilityChanges &&
+ state.visible &&
+ !movedBackgroundLayer
+ ) {
+ // The contracting view is now visible, so the closing app is not. If this is
+ // the first frame when it happens, trigger a one-off sync and move the
+ // background layer in its new container.
+ movedBackgroundLayer = true
+
+ openingWindowSyncViewOverlay!!.remove(windowBackgroundLayer)
+ transitionContainerOverlay.add(windowBackgroundLayer)
+
+ ViewRootSync.synchronizeNextDraw(
+ openingWindowSyncView,
+ transitionContainer,
+ then = {}
+ )
}
val container =
@@ -376,19 +447,14 @@
state,
linearProgress,
container,
- fadeOutWindowBackgroundLayer,
- drawHole
+ fadeWindowBackgroundLayer,
+ drawHole,
+ controller.isLaunching
)
controller.onTransitionAnimationProgress(state, progress, linearProgress)
}
- animator.start()
- return object : Animation {
- override fun cancel() {
- cancelled = true
- animator.cancel()
- }
- }
+ return animator
}
/** Return whether we are expanding fully above the [transitionContainer]. */
@@ -405,8 +471,9 @@
state: State,
linearProgress: Float,
transitionContainer: View,
- fadeOutWindowBackgroundLayer: Boolean,
- drawHole: Boolean
+ fadeWindowBackgroundLayer: Boolean,
+ drawHole: Boolean,
+ isLaunching: Boolean
) {
// Update position.
transitionContainer.getLocationOnScreen(transitionContainerLocation)
@@ -437,27 +504,64 @@
timings.contentBeforeFadeOutDelay,
timings.contentBeforeFadeOutDuration
)
- if (fadeInProgress < 1) {
- val alpha =
- interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- } else if (fadeOutWindowBackgroundLayer) {
- val fadeOutProgress =
- getProgress(
- timings,
- linearProgress,
- timings.contentAfterFadeInDelay,
- timings.contentAfterFadeInDuration
- )
- val alpha =
- 1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
- drawable.alpha = (alpha * 0xFF).roundToInt()
- if (drawHole) {
- drawable.setXfermode(SRC_MODE)
+ if (isLaunching) {
+ if (fadeInProgress < 1) {
+ val alpha =
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+ } else if (fadeWindowBackgroundLayer) {
+ val fadeOutProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
+ val alpha =
+ 1 -
+ interpolators.contentAfterFadeInInterpolator.getInterpolation(
+ fadeOutProgress
+ )
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+
+ if (drawHole) {
+ drawable.setXfermode(SRC_MODE)
+ }
+ } else {
+ drawable.alpha = 0xFF
}
} else {
- drawable.alpha = 0xFF
+ if (fadeInProgress < 1 && fadeWindowBackgroundLayer) {
+ val alpha =
+ interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+
+ if (drawHole) {
+ drawable.setXfermode(SRC_MODE)
+ }
+ } else {
+ val fadeOutProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
+ val alpha =
+ 1 -
+ interpolators.contentAfterFadeInInterpolator.getInterpolation(
+ fadeOutProgress
+ )
+ drawable.alpha = (alpha * 0xFF).roundToInt()
+ drawable.setXfermode(null)
+ }
+ }
+ }
+
+ private fun checkReturnAnimationFrameworkFlag() {
+ check(returnAnimationFrameworkLibrary()) {
+ "isLaunching cannot be false when the returnAnimationFrameworkLibrary flag is disabled"
}
}
}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 974ee3a..c7f0a96 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -168,6 +168,9 @@
override var transitionContainer: ViewGroup = composeViewRoot.rootView as ViewGroup
+ // TODO(b/323863002): update to be dependant on usage.
+ override val isLaunching: Boolean = true
+
override fun onTransitionAnimationEnd(isExpandingFullyAbove: Boolean) {
animatorState.value = null
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 182798e..4eb3a17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -178,8 +178,6 @@
import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
import com.android.wm.shell.keyguard.KeyguardTransitions;
-import dagger.Lazy;
-
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -189,6 +187,7 @@
import java.util.concurrent.Executor;
import java.util.function.Consumer;
+import dagger.Lazy;
import kotlinx.coroutines.CoroutineDispatcher;
/**
@@ -964,6 +963,13 @@
@VisibleForTesting
final ActivityTransitionAnimator.Controller mOccludeAnimationController =
new ActivityTransitionAnimator.Controller() {
+ private boolean mIsLaunching = true;
+
+ @Override
+ public boolean isLaunching() {
+ return mIsLaunching;
+ }
+
@Override
public void onTransitionAnimationStart(boolean isExpandingFullyAbove) {
mOccludeAnimationPlaying = true;
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
index aab90c3..585bd6a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerOcclusionManager.kt
@@ -263,6 +263,7 @@
@VisibleForTesting
val occludeAnimationController: ActivityTransitionAnimator.Controller =
object : ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
override var transitionContainer: ViewGroup
get() = keyguardViewController.get().getViewRootImpl().view as ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index eb0870a..2b7df7d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -75,6 +75,8 @@
private val notificationEntry = notification.entry
private val notificationKey = notificationEntry.sbn.key
+ override val isLaunching: Boolean = true
+
override var transitionContainer: ViewGroup
get() = notification.rootView as ViewGroup
set(ignored) {
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index 572a6c1..0dbbe63 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -224,6 +224,5 @@
<instrumentation android:name="android.testing.TestableInstrumentation"
android:targetPackage="com.android.systemui.tests"
- android:label="Tests for SystemUI">
- </instrumentation>
+ android:label="Tests for SystemUI" />
</manifest>
diff --git a/packages/SystemUI/tests/AndroidTest.xml b/packages/SystemUI/tests/AndroidTest.xml
index cd2a62d..2de5faf 100644
--- a/packages/SystemUI/tests/AndroidTest.xml
+++ b/packages/SystemUI/tests/AndroidTest.xml
@@ -23,6 +23,15 @@
<option name="force-root" value="true" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="screen-always-on" value="on" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
+
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="framework-base-presubmit" />
<option name="test-tag" value="SystemUITests" />
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
new file mode 100644
index 0000000..60bff17
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
new file mode 100644
index 0000000..ea768c0
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 255,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
new file mode 100644
index 0000000..608e633
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
new file mode 100644
index 0000000..608e633
--- /dev/null
+++ b/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
@@ -0,0 +1,393 @@
+{
+ "frame_ids": [
+ "before",
+ 0,
+ 26,
+ 52,
+ 78,
+ 105,
+ 131,
+ 157,
+ 184,
+ 210,
+ 236,
+ 263,
+ 289,
+ 315,
+ 342,
+ 368,
+ 394,
+ 421,
+ 447,
+ 473,
+ 500
+ ],
+ "features": [
+ {
+ "name": "bounds",
+ "type": "rect",
+ "data_points": [
+ {
+ "left": 0,
+ "top": 0,
+ "right": 0,
+ "bottom": 0
+ },
+ {
+ "left": 100,
+ "top": 300,
+ "right": 200,
+ "bottom": 400
+ },
+ {
+ "left": 98,
+ "top": 293,
+ "right": 203,
+ "bottom": 407
+ },
+ {
+ "left": 91,
+ "top": 269,
+ "right": 213,
+ "bottom": 430
+ },
+ {
+ "left": 71,
+ "top": 206,
+ "right": 240,
+ "bottom": 491
+ },
+ {
+ "left": 34,
+ "top": 98,
+ "right": 283,
+ "bottom": 595
+ },
+ {
+ "left": 22,
+ "top": 63,
+ "right": 296,
+ "bottom": 629
+ },
+ {
+ "left": 15,
+ "top": 44,
+ "right": 303,
+ "bottom": 648
+ },
+ {
+ "left": 11,
+ "top": 32,
+ "right": 308,
+ "bottom": 659
+ },
+ {
+ "left": 8,
+ "top": 23,
+ "right": 311,
+ "bottom": 667
+ },
+ {
+ "left": 6,
+ "top": 18,
+ "right": 313,
+ "bottom": 673
+ },
+ {
+ "left": 5,
+ "top": 13,
+ "right": 315,
+ "bottom": 677
+ },
+ {
+ "left": 3,
+ "top": 9,
+ "right": 316,
+ "bottom": 681
+ },
+ {
+ "left": 2,
+ "top": 7,
+ "right": 317,
+ "bottom": 683
+ },
+ {
+ "left": 2,
+ "top": 5,
+ "right": 318,
+ "bottom": 685
+ },
+ {
+ "left": 1,
+ "top": 3,
+ "right": 319,
+ "bottom": 687
+ },
+ {
+ "left": 1,
+ "top": 2,
+ "right": 319,
+ "bottom": 688
+ },
+ {
+ "left": 0,
+ "top": 1,
+ "right": 320,
+ "bottom": 689
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ },
+ {
+ "left": 0,
+ "top": 0,
+ "right": 320,
+ "bottom": 690
+ }
+ ]
+ },
+ {
+ "name": "corner_radii",
+ "type": "cornerRadii",
+ "data_points": [
+ null,
+ {
+ "top_left_x": 10,
+ "top_left_y": 10,
+ "top_right_x": 10,
+ "top_right_y": 10,
+ "bottom_right_x": 20,
+ "bottom_right_y": 20,
+ "bottom_left_x": 20,
+ "bottom_left_y": 20
+ },
+ {
+ "top_left_x": 9.762664,
+ "top_left_y": 9.762664,
+ "top_right_x": 9.762664,
+ "top_right_y": 9.762664,
+ "bottom_right_x": 19.525328,
+ "bottom_right_y": 19.525328,
+ "bottom_left_x": 19.525328,
+ "bottom_left_y": 19.525328
+ },
+ {
+ "top_left_x": 8.969244,
+ "top_left_y": 8.969244,
+ "top_right_x": 8.969244,
+ "top_right_y": 8.969244,
+ "bottom_right_x": 17.938488,
+ "bottom_right_y": 17.938488,
+ "bottom_left_x": 17.938488,
+ "bottom_left_y": 17.938488
+ },
+ {
+ "top_left_x": 6.8709626,
+ "top_left_y": 6.8709626,
+ "top_right_x": 6.8709626,
+ "top_right_y": 6.8709626,
+ "bottom_right_x": 13.741925,
+ "bottom_right_y": 13.741925,
+ "bottom_left_x": 13.741925,
+ "bottom_left_y": 13.741925
+ },
+ {
+ "top_left_x": 3.260561,
+ "top_left_y": 3.260561,
+ "top_right_x": 3.260561,
+ "top_right_y": 3.260561,
+ "bottom_right_x": 6.521122,
+ "bottom_right_y": 6.521122,
+ "bottom_left_x": 6.521122,
+ "bottom_left_y": 6.521122
+ },
+ {
+ "top_left_x": 2.0915751,
+ "top_left_y": 2.0915751,
+ "top_right_x": 2.0915751,
+ "top_right_y": 2.0915751,
+ "bottom_right_x": 4.1831503,
+ "bottom_right_y": 4.1831503,
+ "bottom_left_x": 4.1831503,
+ "bottom_left_y": 4.1831503
+ },
+ {
+ "top_left_x": 1.4640827,
+ "top_left_y": 1.4640827,
+ "top_right_x": 1.4640827,
+ "top_right_y": 1.4640827,
+ "bottom_right_x": 2.9281654,
+ "bottom_right_y": 2.9281654,
+ "bottom_left_x": 2.9281654,
+ "bottom_left_y": 2.9281654
+ },
+ {
+ "top_left_x": 1.057313,
+ "top_left_y": 1.057313,
+ "top_right_x": 1.057313,
+ "top_right_y": 1.057313,
+ "bottom_right_x": 2.114626,
+ "bottom_right_y": 2.114626,
+ "bottom_left_x": 2.114626,
+ "bottom_left_y": 2.114626
+ },
+ {
+ "top_left_x": 0.7824335,
+ "top_left_y": 0.7824335,
+ "top_right_x": 0.7824335,
+ "top_right_y": 0.7824335,
+ "bottom_right_x": 1.564867,
+ "bottom_right_y": 1.564867,
+ "bottom_left_x": 1.564867,
+ "bottom_left_y": 1.564867
+ },
+ {
+ "top_left_x": 0.5863056,
+ "top_left_y": 0.5863056,
+ "top_right_x": 0.5863056,
+ "top_right_y": 0.5863056,
+ "bottom_right_x": 1.1726112,
+ "bottom_right_y": 1.1726112,
+ "bottom_left_x": 1.1726112,
+ "bottom_left_y": 1.1726112
+ },
+ {
+ "top_left_x": 0.4332962,
+ "top_left_y": 0.4332962,
+ "top_right_x": 0.4332962,
+ "top_right_y": 0.4332962,
+ "bottom_right_x": 0.8665924,
+ "bottom_right_y": 0.8665924,
+ "bottom_left_x": 0.8665924,
+ "bottom_left_y": 0.8665924
+ },
+ {
+ "top_left_x": 0.3145876,
+ "top_left_y": 0.3145876,
+ "top_right_x": 0.3145876,
+ "top_right_y": 0.3145876,
+ "bottom_right_x": 0.6291752,
+ "bottom_right_y": 0.6291752,
+ "bottom_left_x": 0.6291752,
+ "bottom_left_y": 0.6291752
+ },
+ {
+ "top_left_x": 0.22506618,
+ "top_left_y": 0.22506618,
+ "top_right_x": 0.22506618,
+ "top_right_y": 0.22506618,
+ "bottom_right_x": 0.45013237,
+ "bottom_right_y": 0.45013237,
+ "bottom_left_x": 0.45013237,
+ "bottom_left_y": 0.45013237
+ },
+ {
+ "top_left_x": 0.15591621,
+ "top_left_y": 0.15591621,
+ "top_right_x": 0.15591621,
+ "top_right_y": 0.15591621,
+ "bottom_right_x": 0.31183243,
+ "bottom_right_y": 0.31183243,
+ "bottom_left_x": 0.31183243,
+ "bottom_left_y": 0.31183243
+ },
+ {
+ "top_left_x": 0.100948334,
+ "top_left_y": 0.100948334,
+ "top_right_x": 0.100948334,
+ "top_right_y": 0.100948334,
+ "bottom_right_x": 0.20189667,
+ "bottom_right_y": 0.20189667,
+ "bottom_left_x": 0.20189667,
+ "bottom_left_y": 0.20189667
+ },
+ {
+ "top_left_x": 0.06496239,
+ "top_left_y": 0.06496239,
+ "top_right_x": 0.06496239,
+ "top_right_y": 0.06496239,
+ "bottom_right_x": 0.12992477,
+ "bottom_right_y": 0.12992477,
+ "bottom_left_x": 0.12992477,
+ "bottom_left_y": 0.12992477
+ },
+ {
+ "top_left_x": 0.03526497,
+ "top_left_y": 0.03526497,
+ "top_right_x": 0.03526497,
+ "top_right_y": 0.03526497,
+ "bottom_right_x": 0.07052994,
+ "bottom_right_y": 0.07052994,
+ "bottom_left_x": 0.07052994,
+ "bottom_left_y": 0.07052994
+ },
+ {
+ "top_left_x": 0.014661789,
+ "top_left_y": 0.014661789,
+ "top_right_x": 0.014661789,
+ "top_right_y": 0.014661789,
+ "bottom_right_x": 0.029323578,
+ "bottom_right_y": 0.029323578,
+ "bottom_left_x": 0.029323578,
+ "bottom_left_y": 0.029323578
+ },
+ {
+ "top_left_x": 0.0041856766,
+ "top_left_y": 0.0041856766,
+ "top_right_x": 0.0041856766,
+ "top_right_y": 0.0041856766,
+ "bottom_right_x": 0.008371353,
+ "bottom_right_y": 0.008371353,
+ "bottom_left_x": 0.008371353,
+ "bottom_left_y": 0.008371353
+ },
+ {
+ "top_left_x": 0,
+ "top_left_y": 0,
+ "top_right_x": 0,
+ "top_right_y": 0,
+ "bottom_right_x": 0,
+ "bottom_right_y": 0,
+ "bottom_left_x": 0,
+ "bottom_left_y": 0
+ }
+ ]
+ },
+ {
+ "name": "alpha",
+ "type": "int",
+ "data_points": [
+ 0,
+ 0,
+ 115,
+ 178,
+ 217,
+ 241,
+ 253,
+ 239,
+ 183,
+ 135,
+ 91,
+ 53,
+ 23,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 75a49d7..41974f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -246,6 +246,8 @@
*/
private class TestTransitionAnimatorController(override var transitionContainer: ViewGroup) :
ActivityTransitionAnimator.Controller {
+ override val isLaunching: Boolean = true
+
override fun createAnimatorState() =
TransitionAnimator.State(
top = 100,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
new file mode 100644
index 0000000..c380a51
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2024 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.animation
+
+import android.animation.AnimatorSet
+import android.graphics.drawable.GradientDrawable
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.EmptyTestActivity
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.motion.RecordedMotion
+import platform.test.motion.Sampling.Companion.evenlySampled
+import platform.test.motion.view.DrawableFeatureCaptures
+import platform.test.motion.view.ViewMotionTestRule
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.GoldenPathManager
+import platform.test.screenshot.PathConfig
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TransitionAnimatorTest : SysuiTestCase() {
+ companion object {
+ private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
+
+ private val emulationSpec =
+ DeviceEmulationSpec(
+ DisplaySpec(
+ "phone",
+ width = 320,
+ height = 690,
+ densityDpi = 160,
+ )
+ )
+ }
+
+ private val pathManager = GoldenPathManager(context, GOLDENS_PATH, pathConfig = PathConfig())
+ private val transitionAnimator =
+ TransitionAnimator(
+ ActivityTransitionAnimator.TIMINGS,
+ ActivityTransitionAnimator.INTERPOLATORS
+ )
+
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
+ @get:Rule(order = 2)
+ val motionRule =
+ ViewMotionTestRule<EmptyTestActivity>(
+ pathManager,
+ { activityRule.scenario },
+ context = context,
+ bitmapDiffer = null,
+ )
+
+ @Test
+ fun backgroundAnimation_whenLaunching() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator = setUpTest(backgroundLayer, isLaunching = true)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimation_whenReturning() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator = setUpTest(backgroundLayer, isLaunching = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimationWithoutFade_whenLaunching() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator =
+ setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ @Test
+ fun backgroundAnimationWithoutFade_whenReturning() {
+ val backgroundLayer = GradientDrawable().apply { alpha = 0 }
+ val animator =
+ setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false)
+
+ val recordedMotion = recordMotion(backgroundLayer, animator)
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden()
+ }
+
+ private fun setUpTest(
+ backgroundLayer: GradientDrawable,
+ isLaunching: Boolean,
+ fadeWindowBackgroundLayer: Boolean = true,
+ ): AnimatorSet {
+ lateinit var transitionContainer: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) }
+ activity.setContentView(transitionContainer)
+ }
+ waitForIdleSync()
+
+ val controller = TestController(transitionContainer, isLaunching)
+ val animator =
+ transitionAnimator.createAnimator(
+ controller,
+ createEndState(transitionContainer),
+ backgroundLayer,
+ fadeWindowBackgroundLayer
+ )
+ return AnimatorSet().apply {
+ duration = animator.duration
+ play(animator)
+ }
+ }
+
+ private fun createEndState(container: ViewGroup): TransitionAnimator.State {
+ val containerLocation = IntArray(2)
+ container.getLocationOnScreen(containerLocation)
+ return TransitionAnimator.State(
+ left = containerLocation[0],
+ top = containerLocation[1],
+ right = containerLocation[0] + emulationSpec.display.width,
+ bottom = containerLocation[1] + emulationSpec.display.height,
+ topCornerRadius = 0f,
+ bottomCornerRadius = 0f
+ )
+ }
+
+ private fun recordMotion(
+ backgroundLayer: GradientDrawable,
+ animator: AnimatorSet
+ ): RecordedMotion {
+ return motionRule.checkThat(animator).record(
+ backgroundLayer,
+ evenlySampled(20),
+ visualCapture = null
+ ) {
+ capture(DrawableFeatureCaptures.bounds, "bounds")
+ capture(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+ capture(DrawableFeatureCaptures.alpha, "alpha")
+ }
+ }
+}
+
+/**
+ * A simple implementation of [TransitionAnimator.Controller] which throws if it is called outside
+ * of the main thread.
+ */
+private class TestController(
+ override var transitionContainer: ViewGroup,
+ override val isLaunching: Boolean
+) : TransitionAnimator.Controller {
+ override fun createAnimatorState(): TransitionAnimator.State {
+ val containerLocation = IntArray(2)
+ transitionContainer.getLocationOnScreen(containerLocation)
+ return TransitionAnimator.State(
+ left = containerLocation[0] + 100,
+ top = containerLocation[1] + 300,
+ right = containerLocation[0] + 200,
+ bottom = containerLocation[1] + 400,
+ topCornerRadius = 10f,
+ bottomCornerRadius = 20f
+ )
+ }
+}