diff options
Diffstat (limited to 'tests')
15 files changed, 638 insertions, 5 deletions
diff --git a/tests/FlickerTests/Android.bp b/tests/FlickerTests/Android.bp index 27e9ffa4cea5..1e997b386faa 100644 --- a/tests/FlickerTests/Android.bp +++ b/tests/FlickerTests/Android.bp @@ -47,7 +47,6 @@ java_defaults { java_library { name: "wm-flicker-common-assertions", - platform_apis: true, optimize: { enabled: false, }, diff --git a/tests/FlickerTests/test-apps/app-helpers/Android.bp b/tests/FlickerTests/test-apps/app-helpers/Android.bp index fc4d71c652d5..e8bb64aa6c55 100644 --- a/tests/FlickerTests/test-apps/app-helpers/Android.bp +++ b/tests/FlickerTests/test-apps/app-helpers/Android.bp @@ -25,7 +25,6 @@ package { java_library { name: "wm-flicker-common-app-helpers", - platform_apis: true, optimize: { enabled: false, }, diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt index 634b6eedd7e6..8d27c1d1dfd1 100644 --- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt +++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/LetterboxAppHelper.kt @@ -33,9 +33,9 @@ class LetterboxAppHelper @JvmOverloads constructor( instr: Instrumentation, - launcherName: String = ActivityOptions.NonResizeablePortraitActivity.LABEL, + launcherName: String = ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.LABEL, component: ComponentNameMatcher = - ActivityOptions.NonResizeablePortraitActivity.COMPONENT.toFlickerComponent() + ActivityOptions.NonResizeableFixedAspectRatioPortraitActivity.COMPONENT.toFlickerComponent() ) : StandardAppHelper(instr, launcherName, component) { private val gestureHelper: GestureHelper = GestureHelper(instrumentation) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index f891606f0066..f2e34257ef01 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -115,6 +115,19 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".NonResizeableFixedAspectRatioPortraitActivity" + android:theme="@style/CutoutNever" + android:resizeableActivity="false" + android:screenOrientation="portrait" + android:minAspectRatio="1.77" + android:taskAffinity="com.android.server.wm.flicker.testapp.NonResizeableFixedAspectRatioPortraitActivity" + android:label="NonResizeableFixedAspectRatioPortraitActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".StartMediaProjectionActivity" android:theme="@style/CutoutNever" android:resizeableActivity="false" @@ -143,6 +156,7 @@ <activity android:name=".LaunchTransparentActivity" android:resizeableActivity="false" android:screenOrientation="portrait" + android:minAspectRatio="1.77" android:theme="@style/OptOutEdgeToEdge" android:taskAffinity="com.android.server.wm.flicker.testapp.LaunchTransparentActivity" android:label="LaunchTransparentActivity" diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index e4de2c574553..73625da9dfa5 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -85,6 +85,12 @@ public class ActivityOptions { FLICKER_APP_PACKAGE + ".NonResizeablePortraitActivity"); } + public static class NonResizeableFixedAspectRatioPortraitActivity { + public static final String LABEL = "NonResizeableFixedAspectRatioPortraitActivity"; + public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".NonResizeableFixedAspectRatioPortraitActivity"); + } + public static class StartMediaProjectionActivity { public static final String LABEL = "StartMediaProjectionActivity"; public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java new file mode 100644 index 000000000000..be38c259d00d --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/NonResizeableFixedAspectRatioPortraitActivity.java @@ -0,0 +1,28 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.os.Bundle; + +public class NonResizeableFixedAspectRatioPortraitActivity extends Activity { + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + setContentView(R.layout.activity_non_resizeable); + } +} diff --git a/tests/graphics/SilkFX/res/layout/view_blur_behind.xml b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml new file mode 100644 index 000000000000..83b1fa4b73cb --- /dev/null +++ b/tests/graphics/SilkFX/res/layout/view_blur_behind.xml @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="I'm a little teapot" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="Something. Something." /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="wowwowwowwowwowwowwowwowwowwowwowwowwowwowwow" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="I'm a little teapot" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="Something. Something." /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="/\\/\\/\\/\\/\\/\\/\\/\\/\\/" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="8dp" + android:textSize="24dp" + android:text="^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^.^" /> + + </LinearLayout> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="300dp" /> + + <com.android.test.silkfx.materials.BlurBehindContainer + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="#33AAAAAA" + android:padding="32dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="48dp" + android:text="Blur!" /> + + </com.android.test.silkfx.materials.BlurBehindContainer> + + <View + android:layout_width="match_parent" + android:layout_height="1024dp" /> + + </LinearLayout> + + </ScrollView> + +</FrameLayout>
\ No newline at end of file diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt index 59a6078376cf..6b6d3b8d3d12 100644 --- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt +++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt @@ -61,7 +61,8 @@ private val AllDemos = listOf( )), DemoGroup("Materials", listOf( Demo("Glass", GlassActivity::class), - Demo("Background Blur", BackgroundBlurActivity::class) + Demo("Background Blur", BackgroundBlurActivity::class), + Demo("View blur behind", R.layout.view_blur_behind, commonControls = false) )) ) diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt new file mode 100644 index 000000000000..ce6348e32969 --- /dev/null +++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/materials/BlurBehindContainer.kt @@ -0,0 +1,30 @@ +/* + * 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.test.silkfx.materials + +import android.content.Context +import android.graphics.RenderEffect +import android.graphics.Shader +import android.util.AttributeSet +import android.widget.FrameLayout + +class BlurBehindContainer(context: Context, attributeSet: AttributeSet) : FrameLayout(context, attributeSet) { + override fun onFinishInflate() { + super.onFinishInflate() + setBackdropRenderEffect( + RenderEffect.createBlurEffect(16.0f, 16.0f, Shader.TileMode.CLAMP)) + } +}
\ No newline at end of file diff --git a/tests/testables/Android.bp b/tests/testables/Android.bp index f2111856c666..17cc0b2a5884 100644 --- a/tests/testables/Android.bp +++ b/tests/testables/Android.bp @@ -35,4 +35,8 @@ java_library { "androidx.test.rules", "mockito-target-inline-minus-junit4", ], + static_libs: [ + "PlatformMotionTesting", + "kotlinx_coroutines_test", + ], } diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt new file mode 100644 index 000000000000..b27b8269575b --- /dev/null +++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt @@ -0,0 +1,159 @@ +/* + * 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 android.animation + +import android.animation.AnimatorTestRuleToolkit.Companion.TAG +import android.util.Log +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.take +import kotlinx.coroutines.flow.takeWhile +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import platform.test.motion.MotionTestRule +import platform.test.motion.RecordedMotion +import platform.test.motion.RecordedMotion.Companion.create +import platform.test.motion.golden.DataPoint +import platform.test.motion.golden.Feature +import platform.test.motion.golden.FrameId +import platform.test.motion.golden.TimeSeries +import platform.test.motion.golden.TimeSeriesCaptureScope +import platform.test.motion.golden.TimestampFrameId + +class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) { + internal companion object { + const val TAG = "AnimatorRuleToolkit" + } +} + +/** + * Controls the timing of the motion recording. + * + * The time series is recorded while the [recording] function is running. + */ +class MotionControl(val recording: MotionControlFn) + +typealias MotionControlFn = suspend MotionControlScope.() -> Unit + +interface MotionControlScope { + /** Waits until [check] returns true. Invoked on each frame. */ + suspend fun awaitCondition(check: () -> Boolean) + + /** Waits for [count] frames to be processed. */ + suspend fun awaitFrames(count: Int = 1) +} + +/** Defines the sampling of features during a test run. */ +data class AnimatorRuleRecordingSpec<T>( + /** The root `observing` object, available in [timeSeriesCapture]'s [TimeSeriesCaptureScope]. */ + val captureRoot: T, + + /** The timing for the recording. */ + val motionControl: MotionControl, + + /** Time interval between frame captures, in milliseconds. */ + val frameDurationMs: Long = 16L, + + /** Produces the time-series, invoked on each animation frame. */ + val timeSeriesCapture: TimeSeriesCaptureScope<T>.() -> Unit, +) + +/** Records the time-series of the features specified in [recordingSpec]. */ +fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion( + recordingSpec: AnimatorRuleRecordingSpec<T>, +): RecordedMotion { + with(toolkit.animatorTestRule) { + val frameIdCollector = mutableListOf<FrameId>() + val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>() + + fun recordFrame(frameId: FrameId) { + Log.i(TAG, "recordFrame($frameId)") + frameIdCollector.add(frameId) + recordingSpec.timeSeriesCapture.invoke( + TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector) + ) + } + + val motionControl = + MotionControlImpl( + toolkit.animatorTestRule, + toolkit.testScope, + recordingSpec.frameDurationMs, + recordingSpec.motionControl, + ) + + Log.i(TAG, "recordMotion() begin recording") + + val startFrameTime = currentTime + while (!motionControl.recordingEnded) { + recordFrame(TimestampFrameId(currentTime - startFrameTime)) + motionControl.nextFrame() + } + + Log.i(TAG, "recordMotion() end recording") + + val timeSeries = + TimeSeries( + frameIdCollector.toList(), + propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) }, + ) + + return create(timeSeries, null) + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +private class MotionControlImpl( + val animatorTestRule: AnimatorTestRule, + val testScope: TestScope, + val frameMs: Long, + motionControl: MotionControl, +) : MotionControlScope { + private val recordingJob = motionControl.recording.launch() + + private val frameEmitter = MutableStateFlow<Long>(0) + private val onFrame = frameEmitter.asStateFlow() + + var recordingEnded: Boolean = false + + fun nextFrame() { + animatorTestRule.advanceTimeBy(frameMs) + + frameEmitter.tryEmit(animatorTestRule.currentTime) + testScope.runCurrent() + + if (recordingJob.isCompleted) { + recordingEnded = true + } + } + + override suspend fun awaitCondition(check: () -> Boolean) { + onFrame.takeWhile { !check() }.collect {} + } + + override suspend fun awaitFrames(count: Int) { + onFrame.take(count).collect {} + } + + private fun MotionControlFn.launch(): Job { + val function = this + return testScope.launch { function() } + } +} diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp index c23f41a6c3d4..71105646a3d3 100644 --- a/tests/testables/tests/Android.bp +++ b/tests/testables/tests/Android.bp @@ -29,13 +29,17 @@ android_test { "src/**/*.kt", "src/**/I*.aidl", ], + asset_dirs: ["goldens"], resource_dirs: ["res"], static_libs: [ + "PlatformMotionTesting", "androidx.core_core-animation", "androidx.core_core-ktx", + "androidx.test.ext.junit", "androidx.test.rules", "androidx.test.ext.junit", "hamcrest-library", + "kotlinx_coroutines_test", "mockito-target-inline-minus-junit4", "testables", "truth", diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordMotion_withAnimator.json new file mode 100644 index 000000000000..87fece5395e7 --- /dev/null +++ b/tests/testables/tests/goldens/recordMotion_withAnimator.json @@ -0,0 +1,64 @@ +{ + "frame_ids": [ + 0, + 20, + 40, + 60, + 80, + 100, + 120, + 140, + 160, + 180, + 200, + 220, + 240, + 260, + 280, + 300, + 320, + 340, + 360, + 380, + 400, + 420, + 440, + 460, + 480, + 500 + ], + "features": [ + { + "name": "value", + "type": "float", + "data_points": [ + 1, + 0.9960574, + 0.98429155, + 0.9648882, + 0.9381534, + 0.9045085, + 0.8644843, + 0.818712, + 0.76791346, + 0.7128896, + 0.65450853, + 0.5936906, + 0.5313952, + 0.46860474, + 0.40630943, + 0.34549147, + 0.2871104, + 0.23208654, + 0.181288, + 0.13551569, + 0.09549153, + 0.061846733, + 0.035111785, + 0.015708387, + 0.003942609, + 0 + ] + } + ] +} diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordMotion_withSpring.json new file mode 100644 index 000000000000..e9fb5b4af869 --- /dev/null +++ b/tests/testables/tests/goldens/recordMotion_withSpring.json @@ -0,0 +1,48 @@ +{ + "frame_ids": [ + 0, + 16, + 32, + 48, + 64, + 80, + 96, + 112, + 128, + 144, + 160, + 176, + 192, + 208, + 224, + 240, + 256, + 272 + ], + "features": [ + { + "name": "value", + "type": "float", + "data_points": [ + 1, + 0.9488604, + 0.83574325, + 0.7016156, + 0.5691678, + 0.4497436, + 0.34789434, + 0.26431116, + 0.19766562, + 0.14572789, + 0.10601636, + 0.076149896, + 0.05401709, + 0.037837274, + 0.026161024, + 0.017839976, + 0.011983856, + 0.007914998 + ] + } + ] +} diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt new file mode 100644 index 000000000000..fbef4899bca9 --- /dev/null +++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt @@ -0,0 +1,129 @@ +/* + * 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 android.animation + +import android.util.FloatProperty +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.dynamicanimation.animation.SpringAnimation +import com.android.internal.dynamicanimation.animation.SpringForce +import kotlinx.coroutines.test.TestScope +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import platform.test.motion.MotionTestRule +import platform.test.motion.RecordedMotion +import platform.test.motion.golden.FeatureCapture +import platform.test.motion.golden.asDataPoint +import platform.test.motion.testing.createGoldenPathManager + +@SmallTest +@RunWith(AndroidJUnit4::class) +class AnimatorTestRuleToolkitTest { + companion object { + private val GOLDEN_PATH_MANAGER = + createGoldenPathManager("frameworks/base/tests/testables/tests/goldens") + + private val TEST_PROPERTY = + object : FloatProperty<TestState>("value") { + override fun get(state: TestState): Float { + return state.animatedValue + } + + override fun setValue(state: TestState, value: Float) { + state.animatedValue = value + } + } + } + + @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this) + @get:Rule(order = 1) + val motionRule = + MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER) + + @Test + fun recordMotion_withAnimator() { + val state = TestState() + AnimatorSet().apply { + duration = 500 + play( + ValueAnimator.ofFloat(state.animatedValue, 0f).apply { + addUpdateListener { state.animatedValue = it.animatedValue as Float } + } + ) + getInstrumentation().runOnMainSync { start() } + } + + val recordedMotion = + record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L) + + motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator") + } + + @Test + fun recordMotion_withSpring() { + val state = TestState() + var isDone = false + SpringAnimation(state, TEST_PROPERTY).apply { + spring = + SpringForce(0f).apply { + stiffness = 500f + dampingRatio = 0.95f + } + + setStartValue(1f) + setMinValue(0f) + setMaxValue(1f) + minimumVisibleChange = 0.01f + + addEndListener { _, _, _, _ -> isDone = true } + getInstrumentation().runOnMainSync { start() } + } + + val recordedMotion = + record(state, MotionControl { awaitCondition { isDone } }, sampleIntervalMs = 16L) + + motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withSpring") + } + + private fun record( + state: TestState, + motionControl: MotionControl, + sampleIntervalMs: Long, + ): RecordedMotion { + var recordedMotion: RecordedMotion? = null + getInstrumentation().runOnMainSync { + recordedMotion = + motionRule.recordMotion( + AnimatorRuleRecordingSpec( + state, + motionControl, + sampleIntervalMs, + ) { + feature( + FeatureCapture("value") { state -> state.animatedValue.asDataPoint() }, + "value", + ) + } + ) + } + return recordedMotion!! + } + + data class TestState(var animatedValue: Float = 1f) +} |