Loading packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/testing/ElementStateAccess.kt +9 −0 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package com.android.compose.animation.scene.testing import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.Element.Companion.AlphaUnspecified import com.android.compose.animation.scene.Element.Companion.SizeUnspecified import com.android.compose.animation.scene.ElementModifier import com.android.compose.animation.scene.Scale Loading @@ -28,6 +31,12 @@ val SemanticsNode.lastAlphaForTesting: Float? val SemanticsNode.lastScaleForTesting: Scale? get() = elementState.lastScale.takeIf { it != Scale.Unspecified } val SemanticsNode.lastOffsetForTesting: Offset? get() = elementState.lastOffset.takeIf { it != Offset.Unspecified } val SemanticsNode.lastSizeForTesting: IntSize? get() = elementState.lastSize.takeIf { it != SizeUnspecified } private val SemanticsNode.elementState: Element.State get() { val elementModifier = Loading packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/DataPointTypes.kt 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isFinite import androidx.compose.ui.geometry.isUnspecified import org.json.JSONObject import platform.test.motion.golden.DataPointType import platform.test.motion.golden.UnknownTypeException fun Scale.asDataPoint() = DataPointTypes.scale.makeDataPoint(this) object DataPointTypes { val scale: DataPointType<Scale> = DataPointType( "scale", jsonToValue = { when (it) { "unspecified" -> Scale.Unspecified "default" -> Scale.Default "zero" -> Scale.Zero is JSONObject -> { val pivot = it.get("pivot") Scale( scaleX = it.getDouble("x").toFloat(), scaleY = it.getDouble("y").toFloat(), pivot = when (pivot) { "unspecified" -> Offset.Unspecified "infinite" -> Offset.Infinite is JSONObject -> Offset( pivot.getDouble("x").toFloat(), pivot.getDouble("y").toFloat(), ) else -> throw UnknownTypeException() }, ) } else -> throw UnknownTypeException() } }, valueToJson = { when (it) { Scale.Unspecified -> "unspecified" Scale.Default -> "default" Scale.Zero -> "zero" else -> { JSONObject().apply { put("x", it.scaleX) put("y", it.scaleY) put( "pivot", when { it.pivot.isUnspecified -> "unspecified" !it.pivot.isFinite -> "infinite" else -> JSONObject().apply { put("x", it.pivot.x) put("y", it.pivot.y) } }, ) } } } }, ) } packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/FeatureCaptures.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.DataPointTypes.scale import com.android.compose.animation.scene.testing.lastAlphaForTesting import com.android.compose.animation.scene.testing.lastOffsetForTesting import com.android.compose.animation.scene.testing.lastScaleForTesting import com.android.compose.animation.scene.testing.lastSizeForTesting import platform.test.motion.compose.DataPointTypes.intSize import platform.test.motion.compose.DataPointTypes.offset import platform.test.motion.golden.DataPoint import platform.test.motion.golden.DataPointTypes import platform.test.motion.golden.FeatureCapture /** * [FeatureCapture] implementations to record animated state of [SceneTransitionLayout] [Element]. */ object FeatureCaptures { val elementAlpha = FeatureCapture<SemanticsNode, Float>("alpha") { DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float) } val elementScale = FeatureCapture<SemanticsNode, Scale>("scale") { DataPoint.of(it.lastScaleForTesting, scale) } val elementOffset = FeatureCapture<SemanticsNode, Offset>("offset") { DataPoint.of(it.lastOffsetForTesting, offset) } val elementSize = FeatureCapture<SemanticsNode, IntSize>("size") { DataPoint.of(it.lastSizeForTesting, intSize) } } packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +3 −82 Original line number Diff line number Diff line Loading @@ -30,22 +30,17 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isFinite import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.FeatureCaptures.elementAlpha import com.android.compose.animation.scene.FeatureCaptures.elementScale import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.isElement import com.android.compose.animation.scene.testing.lastAlphaForTesting import com.android.compose.animation.scene.testing.lastScaleForTesting import com.android.compose.theme.PlatformTheme import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.bouncerInteractor Loading @@ -71,12 +66,12 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.view.sceneJankMonitorFactory import com.android.systemui.testKosmos import kotlin.test.Ignore import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.json.JSONObject import org.junit.Before import org.junit.Rule import org.junit.Test Loading @@ -89,14 +84,8 @@ import platform.test.motion.compose.MotionControl import platform.test.motion.compose.feature import platform.test.motion.compose.recordMotion import platform.test.motion.compose.runTest import platform.test.motion.golden.DataPoint import platform.test.motion.golden.DataPointType import platform.test.motion.golden.DataPointTypes import platform.test.motion.golden.FeatureCapture import platform.test.motion.golden.UnknownTypeException import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.Displays.Phone import kotlin.test.Ignore /** MotionTest for the Bouncer Predictive Back animation */ @LargeTest Loading Loading @@ -280,72 +269,4 @@ class BouncerPredictiveBackTest : SysuiTestCase() { override suspend fun onActivated() = awaitCancellation() } companion object { private val elementAlpha = FeatureCapture<SemanticsNode, Float>("alpha") { DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float) } private val elementScale = FeatureCapture<SemanticsNode, Scale>("scale") { DataPoint.of(it.lastScaleForTesting, scale) } private val scale: DataPointType<Scale> = DataPointType( "scale", jsonToValue = { when (it) { "unspecified" -> Scale.Unspecified "default" -> Scale.Default "zero" -> Scale.Zero is JSONObject -> { val pivot = it.get("pivot") Scale( scaleX = it.getDouble("x").toFloat(), scaleY = it.getDouble("y").toFloat(), pivot = when (pivot) { "unspecified" -> Offset.Unspecified "infinite" -> Offset.Infinite is JSONObject -> Offset( pivot.getDouble("x").toFloat(), pivot.getDouble("y").toFloat(), ) else -> throw UnknownTypeException() }, ) } else -> throw UnknownTypeException() } }, valueToJson = { when (it) { Scale.Unspecified -> "unspecified" Scale.Default -> "default" Scale.Zero -> "zero" else -> { JSONObject().apply { put("x", it.scaleX) put("y", it.scaleY) put( "pivot", when { it.pivot.isUnspecified -> "unspecified" !it.pivot.isFinite -> "infinite" else -> JSONObject().apply { put("x", it.pivot.x) put("y", it.pivot.y) } }, ) } } } }, ) } } Loading
packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/testing/ElementStateAccess.kt +9 −0 Original line number Diff line number Diff line Loading @@ -16,9 +16,12 @@ package com.android.compose.animation.scene.testing import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.Element import com.android.compose.animation.scene.Element.Companion.AlphaUnspecified import com.android.compose.animation.scene.Element.Companion.SizeUnspecified import com.android.compose.animation.scene.ElementModifier import com.android.compose.animation.scene.Scale Loading @@ -28,6 +31,12 @@ val SemanticsNode.lastAlphaForTesting: Float? val SemanticsNode.lastScaleForTesting: Scale? get() = elementState.lastScale.takeIf { it != Scale.Unspecified } val SemanticsNode.lastOffsetForTesting: Offset? get() = elementState.lastOffset.takeIf { it != Offset.Unspecified } val SemanticsNode.lastSizeForTesting: IntSize? get() = elementState.lastSize.takeIf { it != SizeUnspecified } private val SemanticsNode.elementState: Element.State get() { val elementModifier = Loading
packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/DataPointTypes.kt 0 → 100644 +84 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isFinite import androidx.compose.ui.geometry.isUnspecified import org.json.JSONObject import platform.test.motion.golden.DataPointType import platform.test.motion.golden.UnknownTypeException fun Scale.asDataPoint() = DataPointTypes.scale.makeDataPoint(this) object DataPointTypes { val scale: DataPointType<Scale> = DataPointType( "scale", jsonToValue = { when (it) { "unspecified" -> Scale.Unspecified "default" -> Scale.Default "zero" -> Scale.Zero is JSONObject -> { val pivot = it.get("pivot") Scale( scaleX = it.getDouble("x").toFloat(), scaleY = it.getDouble("y").toFloat(), pivot = when (pivot) { "unspecified" -> Offset.Unspecified "infinite" -> Offset.Infinite is JSONObject -> Offset( pivot.getDouble("x").toFloat(), pivot.getDouble("y").toFloat(), ) else -> throw UnknownTypeException() }, ) } else -> throw UnknownTypeException() } }, valueToJson = { when (it) { Scale.Unspecified -> "unspecified" Scale.Default -> "default" Scale.Zero -> "zero" else -> { JSONObject().apply { put("x", it.scaleX) put("y", it.scaleY) put( "pivot", when { it.pivot.isUnspecified -> "unspecified" !it.pivot.isFinite -> "infinite" else -> JSONObject().apply { put("x", it.pivot.x) put("y", it.pivot.y) } }, ) } } } }, ) }
packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/FeatureCaptures.kt 0 → 100644 +57 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.DataPointTypes.scale import com.android.compose.animation.scene.testing.lastAlphaForTesting import com.android.compose.animation.scene.testing.lastOffsetForTesting import com.android.compose.animation.scene.testing.lastScaleForTesting import com.android.compose.animation.scene.testing.lastSizeForTesting import platform.test.motion.compose.DataPointTypes.intSize import platform.test.motion.compose.DataPointTypes.offset import platform.test.motion.golden.DataPoint import platform.test.motion.golden.DataPointTypes import platform.test.motion.golden.FeatureCapture /** * [FeatureCapture] implementations to record animated state of [SceneTransitionLayout] [Element]. */ object FeatureCaptures { val elementAlpha = FeatureCapture<SemanticsNode, Float>("alpha") { DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float) } val elementScale = FeatureCapture<SemanticsNode, Scale>("scale") { DataPoint.of(it.lastScaleForTesting, scale) } val elementOffset = FeatureCapture<SemanticsNode, Offset>("offset") { DataPoint.of(it.lastOffsetForTesting, offset) } val elementSize = FeatureCapture<SemanticsNode, IntSize>("size") { DataPoint.of(it.lastSizeForTesting, intSize) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt +3 −82 Original line number Diff line number Diff line Loading @@ -30,22 +30,17 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.isFinite import androidx.compose.ui.geometry.isUnspecified import androidx.compose.ui.semantics.SemanticsNode import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import com.android.compose.animation.scene.ContentScope import com.android.compose.animation.scene.FeatureCaptures.elementAlpha import com.android.compose.animation.scene.FeatureCaptures.elementScale import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.Scale import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult import com.android.compose.animation.scene.isElement import com.android.compose.animation.scene.testing.lastAlphaForTesting import com.android.compose.animation.scene.testing.lastScaleForTesting import com.android.compose.theme.PlatformTheme import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.bouncerInteractor Loading @@ -71,12 +66,12 @@ import com.android.systemui.scene.ui.composable.Scene import com.android.systemui.scene.ui.composable.SceneContainer import com.android.systemui.scene.ui.view.sceneJankMonitorFactory import com.android.systemui.testKosmos import kotlin.test.Ignore import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import org.json.JSONObject import org.junit.Before import org.junit.Rule import org.junit.Test Loading @@ -89,14 +84,8 @@ import platform.test.motion.compose.MotionControl import platform.test.motion.compose.feature import platform.test.motion.compose.recordMotion import platform.test.motion.compose.runTest import platform.test.motion.golden.DataPoint import platform.test.motion.golden.DataPointType import platform.test.motion.golden.DataPointTypes import platform.test.motion.golden.FeatureCapture import platform.test.motion.golden.UnknownTypeException import platform.test.screenshot.DeviceEmulationSpec import platform.test.screenshot.Displays.Phone import kotlin.test.Ignore /** MotionTest for the Bouncer Predictive Back animation */ @LargeTest Loading Loading @@ -280,72 +269,4 @@ class BouncerPredictiveBackTest : SysuiTestCase() { override suspend fun onActivated() = awaitCancellation() } companion object { private val elementAlpha = FeatureCapture<SemanticsNode, Float>("alpha") { DataPoint.of(it.lastAlphaForTesting, DataPointTypes.float) } private val elementScale = FeatureCapture<SemanticsNode, Scale>("scale") { DataPoint.of(it.lastScaleForTesting, scale) } private val scale: DataPointType<Scale> = DataPointType( "scale", jsonToValue = { when (it) { "unspecified" -> Scale.Unspecified "default" -> Scale.Default "zero" -> Scale.Zero is JSONObject -> { val pivot = it.get("pivot") Scale( scaleX = it.getDouble("x").toFloat(), scaleY = it.getDouble("y").toFloat(), pivot = when (pivot) { "unspecified" -> Offset.Unspecified "infinite" -> Offset.Infinite is JSONObject -> Offset( pivot.getDouble("x").toFloat(), pivot.getDouble("y").toFloat(), ) else -> throw UnknownTypeException() }, ) } else -> throw UnknownTypeException() } }, valueToJson = { when (it) { Scale.Unspecified -> "unspecified" Scale.Default -> "default" Scale.Zero -> "zero" else -> { JSONObject().apply { put("x", it.scaleX) put("y", it.scaleY) put( "pivot", when { it.pivot.isUnspecified -> "unspecified" !it.pivot.isFinite -> "infinite" else -> JSONObject().apply { put("x", it.pivot.x) put("y", it.pivot.y) } }, ) } } } }, ) } }