summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt5
-rw-r--r--tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt76
-rw-r--r--tests/testables/tests/Android.bp4
-rw-r--r--tests/testables/tests/AndroidManifest.xml4
-rw-r--r--tests/testables/tests/AndroidTest.xml51
-rw-r--r--tests/testables/tests/goldens/recordFilmstrip_withAnimator.pngbin0 -> 40500 bytes
-rw-r--r--tests/testables/tests/goldens/recordFilmstrip_withSpring.pngbin0 -> 32206 bytes
-rw-r--r--tests/testables/tests/goldens/recordTimeSeries_withAnimator.json (renamed from tests/testables/tests/goldens/recordMotion_withAnimator.json)2
-rw-r--r--tests/testables/tests/goldens/recordTimeSeries_withSpring.json (renamed from tests/testables/tests/goldens/recordMotion_withSpring.json)2
-rw-r--r--tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt188
10 files changed, 260 insertions, 72 deletions
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 071acfa44650..288ed4dc9d4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -77,7 +77,10 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
@get:Rule(order = 2) val animatorTestRule = android.animation.AnimatorTestRule(this)
@get:Rule(order = 3)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope), pathManager)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, kosmos.testScope) { activityRule.scenario },
+ pathManager,
+ )
@Test
fun backgroundAnimation_whenLaunching() {
diff --git a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
index b27b8269575b..ded467993eef 100644
--- a/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
+++ b/tests/testables/src/android/animation/AnimatorTestRuleToolkit.kt
@@ -17,7 +17,13 @@
package android.animation
import android.animation.AnimatorTestRuleToolkit.Companion.TAG
+import android.graphics.Bitmap
+import android.graphics.drawable.Drawable
import android.util.Log
+import android.view.View
+import androidx.core.graphics.drawable.toBitmap
+import androidx.test.core.app.ActivityScenario
+import java.util.concurrent.TimeUnit
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@@ -36,13 +42,45 @@ import platform.test.motion.golden.FrameId
import platform.test.motion.golden.TimeSeries
import platform.test.motion.golden.TimeSeriesCaptureScope
import platform.test.motion.golden.TimestampFrameId
+import platform.test.screenshot.captureToBitmapAsync
-class AnimatorTestRuleToolkit(val animatorTestRule: AnimatorTestRule, val testScope: TestScope) {
+class AnimatorTestRuleToolkit(
+ internal val animatorTestRule: AnimatorTestRule,
+ internal val testScope: TestScope,
+ internal val currentActivityScenario: () -> ActivityScenario<*>,
+) {
internal companion object {
const val TAG = "AnimatorRuleToolkit"
}
}
+/** Capture utility to extract a [Bitmap] from a [drawable]. */
+fun captureDrawable(drawable: Drawable): Bitmap {
+ val width = drawable.bounds.right - drawable.bounds.left
+ val height = drawable.bounds.bottom - drawable.bounds.top
+
+ // If either dimension is 0 this will fail, so we set it to 1 pixel instead.
+ return drawable.toBitmap(
+ width =
+ if (width > 0) {
+ width
+ } else {
+ 1
+ },
+ height =
+ if (height > 0) {
+ height
+ } else {
+ 1
+ },
+ )
+}
+
+/** Capture utility to extract a [Bitmap] from a [view]. */
+fun captureView(view: View): Bitmap {
+ return view.captureToBitmapAsync().get(10, TimeUnit.SECONDS)
+}
+
/**
* Controls the timing of the motion recording.
*
@@ -71,24 +109,39 @@ data class AnimatorRuleRecordingSpec<T>(
/** Time interval between frame captures, in milliseconds. */
val frameDurationMs: Long = 16L,
- /** Produces the time-series, invoked on each animation frame. */
+ /** Whether a sequence of screenshots should also be recorded. */
+ val visualCapture: ((captureRoot: T) -> Bitmap)? = null,
+
+ /** 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>,
+ recordingSpec: AnimatorRuleRecordingSpec<T>
): RecordedMotion {
with(toolkit.animatorTestRule) {
+ val activityScenario = toolkit.currentActivityScenario()
val frameIdCollector = mutableListOf<FrameId>()
val propertyCollector = mutableMapOf<String, MutableList<DataPoint<*>>>()
+ val screenshotCollector =
+ if (recordingSpec.visualCapture != null) {
+ mutableListOf<Bitmap>()
+ } else {
+ null
+ }
fun recordFrame(frameId: FrameId) {
Log.i(TAG, "recordFrame($frameId)")
frameIdCollector.add(frameId)
- recordingSpec.timeSeriesCapture.invoke(
- TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
- )
+ activityScenario.onActivity {
+ recordingSpec.timeSeriesCapture.invoke(
+ TimeSeriesCaptureScope(recordingSpec.captureRoot, propertyCollector)
+ )
+ }
+
+ val bitmap = recordingSpec.visualCapture?.invoke(recordingSpec.captureRoot)
+ if (bitmap != null) screenshotCollector!!.add(bitmap)
}
val motionControl =
@@ -101,10 +154,13 @@ fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
Log.i(TAG, "recordMotion() begin recording")
- val startFrameTime = currentTime
+ var startFrameTime: Long? = null
+ toolkit.currentActivityScenario().onActivity { startFrameTime = currentTime }
while (!motionControl.recordingEnded) {
- recordFrame(TimestampFrameId(currentTime - startFrameTime))
- motionControl.nextFrame()
+ var time: Long? = null
+ toolkit.currentActivityScenario().onActivity { time = currentTime }
+ recordFrame(TimestampFrameId(time!! - startFrameTime!!))
+ toolkit.currentActivityScenario().onActivity { motionControl.nextFrame() }
}
Log.i(TAG, "recordMotion() end recording")
@@ -115,7 +171,7 @@ fun <T> MotionTestRule<AnimatorTestRuleToolkit>.recordMotion(
propertyCollector.entries.map { entry -> Feature(entry.key, entry.value) },
)
- return create(timeSeries, null)
+ return create(timeSeries, screenshotCollector)
}
}
diff --git a/tests/testables/tests/Android.bp b/tests/testables/tests/Android.bp
index 71105646a3d3..f0cda535b3aa 100644
--- a/tests/testables/tests/Android.bp
+++ b/tests/testables/tests/Android.bp
@@ -37,10 +37,11 @@ android_test {
"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",
+ "platform-screenshot-diff-core",
+ "platform-test-annotations",
"testables",
"truth",
],
@@ -55,6 +56,7 @@ android_test {
"android.test.mock.stubs.system",
],
certificate: "platform",
+ test_config: "AndroidTest.xml",
test_suites: [
"device-tests",
"automotive-tests",
diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml
index 2bfb04fdb765..6cba59872710 100644
--- a/tests/testables/tests/AndroidManifest.xml
+++ b/tests/testables/tests/AndroidManifest.xml
@@ -23,6 +23,10 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+ <activity
+ android:name="platform.test.screenshot.ScreenshotActivity"
+ android:exported="true">
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml
new file mode 100644
index 000000000000..85f6e6257770
--- /dev/null
+++ b/tests/testables/tests/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 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.
+-->
+<configuration description="Runs Tests for Testables.">
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="TestablesTests.apk" />
+ <option name="install-arg" value="-t" />
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <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="TestableTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.testables" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="test-filter-dir" value="/data/data/com.android.testables" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+
+ <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ <option name="directory-keys" value="/data/user/0/com.android.testables/files"/>
+ <option name="collect-on-run-ended-only" value="true"/>
+ <option name="clean-up" value="true"/>
+ </metrics_collector>
+</configuration>
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
new file mode 100644
index 000000000000..9aed2e970239
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withAnimator.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordFilmstrip_withSpring.png b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
new file mode 100644
index 000000000000..1d0c0c3c3393
--- /dev/null
+++ b/tests/testables/tests/goldens/recordFilmstrip_withSpring.png
Binary files differ
diff --git a/tests/testables/tests/goldens/recordMotion_withAnimator.json b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
index 87fece5395e7..73eb6c74fee6 100644
--- a/tests/testables/tests/goldens/recordMotion_withAnimator.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withAnimator.json
@@ -29,7 +29,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/goldens/recordMotion_withSpring.json b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
index e9fb5b4af869..2b97bad08e00 100644
--- a/tests/testables/tests/goldens/recordMotion_withSpring.json
+++ b/tests/testables/tests/goldens/recordTimeSeries_withSpring.json
@@ -21,7 +21,7 @@
],
"features": [
{
- "name": "value",
+ "name": "alpha",
"type": "float",
"data_points": [
1,
diff --git a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
index fbef4899bca9..993c3fed9d59 100644
--- a/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
+++ b/tests/testables/tests/src/android/animation/AnimatorTestRuleToolkitTest.kt
@@ -16,10 +16,15 @@
package android.animation
-import android.util.FloatProperty
+import android.graphics.Color
+import android.platform.test.annotations.MotionTest
+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 androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.dynamicanimation.animation.DynamicAnimation
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import kotlinx.coroutines.test.TestScope
@@ -28,102 +33,169 @@ 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
+import platform.test.motion.view.ViewFeatureCaptures
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+import platform.test.screenshot.ScreenshotActivity
+import platform.test.screenshot.ScreenshotTestRule
@SmallTest
+@MotionTest
@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
- }
- }
+ private val EMULATION_SPEC =
+ DeviceEmulationSpec(DisplaySpec("phone", width = 320, height = 690, densityDpi = 160))
}
- @get:Rule(order = 0) val animatorTestRule = AnimatorTestRule(this)
- @get:Rule(order = 1)
+ @get:Rule(order = 0) val deviceEmulationRule = DeviceEmulationRule(EMULATION_SPEC)
+ @get:Rule(order = 1) val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ @get:Rule(order = 2) val animatorTestRule = AnimatorTestRule(this)
+ @get:Rule(order = 3) val screenshotRule = ScreenshotTestRule(GOLDEN_PATH_MANAGER)
+ @get:Rule(order = 4)
val motionRule =
- MotionTestRule(AnimatorTestRuleToolkit(animatorTestRule, TestScope()), GOLDEN_PATH_MANAGER)
+ MotionTestRule(
+ AnimatorTestRuleToolkit(animatorTestRule, TestScope()) { activityRule.scenario },
+ GOLDEN_PATH_MANAGER,
+ bitmapDiffer = screenshotRule,
+ )
@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 }
- }
+ fun recordFilmstrip_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = true,
+ )
+
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withAnimator")
+ }
+
+ @Test
+ fun recordTimeSeries_withAnimator() {
+ val animatedBox = createScene()
+ createAnimator(animatedBox).apply { getInstrumentation().runOnMainSync { start() } }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitFrames(count = 26) },
+ sampleIntervalMs = 20L,
+ recordScreenshots = false,
)
+
+ motionRule
+ .assertThat(recordedMotion)
+ .timeSeriesMatchesGolden("recordTimeSeries_withAnimator")
+ }
+
+ @Test
+ fun recordFilmstrip_withSpring() {
+ val animatedBox = createScene()
+ var isDone = false
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
getInstrumentation().runOnMainSync { start() }
}
val recordedMotion =
- record(state, MotionControl { awaitFrames(count = 26) }, sampleIntervalMs = 20L)
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = true,
+ )
- motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordMotion_withAnimator")
+ motionRule.assertThat(recordedMotion).filmstripMatchesGolden("recordFilmstrip_withSpring")
}
@Test
- fun recordMotion_withSpring() {
- val state = TestState()
+ fun recordTimeSeries_withSpring() {
+ val animatedBox = createScene()
var isDone = false
- SpringAnimation(state, TEST_PROPERTY).apply {
+ createSpring(animatedBox).apply {
+ addEndListener { _, _, _, _ -> isDone = true }
+ getInstrumentation().runOnMainSync { start() }
+ }
+
+ val recordedMotion =
+ record(
+ animatedBox,
+ MotionControl { awaitCondition { isDone } },
+ sampleIntervalMs = 16L,
+ recordScreenshots = false,
+ )
+
+ motionRule.assertThat(recordedMotion).timeSeriesMatchesGolden("recordTimeSeries_withSpring")
+ }
+
+ private fun createScene(): ViewGroup {
+ lateinit var sceneRoot: ViewGroup
+ activityRule.scenario.onActivity { activity ->
+ sceneRoot = FrameLayout(activity).apply { setBackgroundColor(Color.BLACK) }
+ activity.setContentView(sceneRoot)
+ }
+ getInstrumentation().waitForIdleSync()
+ return sceneRoot
+ }
+
+ private fun createAnimator(animatedBox: ViewGroup): AnimatorSet {
+ return AnimatorSet().apply {
+ duration = 500
+ play(
+ ValueAnimator.ofFloat(animatedBox.alpha, 0f).apply {
+ addUpdateListener { animatedBox.alpha = it.animatedValue as Float }
+ }
+ )
+ }
+ }
+
+ private fun createSpring(animatedBox: ViewGroup): SpringAnimation {
+ return SpringAnimation(animatedBox, DynamicAnimation.ALPHA).apply {
spring =
SpringForce(0f).apply {
stiffness = 500f
dampingRatio = 0.95f
}
- setStartValue(1f)
+ setStartValue(animatedBox.alpha)
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,
+ container: ViewGroup,
motionControl: MotionControl,
sampleIntervalMs: Long,
+ recordScreenshots: Boolean,
): RecordedMotion {
- var recordedMotion: RecordedMotion? = null
- getInstrumentation().runOnMainSync {
- recordedMotion =
- motionRule.recordMotion(
- AnimatorRuleRecordingSpec(
- state,
- motionControl,
- sampleIntervalMs,
- ) {
- feature(
- FeatureCapture("value") { state -> state.animatedValue.asDataPoint() },
- "value",
- )
- }
- )
- }
- return recordedMotion!!
+ val visualCapture =
+ if (recordScreenshots) {
+ ::captureView
+ } else {
+ null
+ }
+ return motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(
+ container,
+ motionControl,
+ sampleIntervalMs,
+ visualCapture,
+ ) {
+ feature(ViewFeatureCaptures.alpha, "alpha")
+ }
+ )
}
-
- data class TestState(var animatedValue: Float = 1f)
}