Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c776b93f authored by Mike Schneider's avatar Mike Schneider
Browse files

Few initial motion tests for both, STL and Flexiglass

Bug: 322324387
Test: Unit tests
Flag: TEST_ONLY
Change-Id: Ic532678b268e16b8300db189d7d15b9580dc9102
parent 944c3298
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -147,6 +147,9 @@ filegroup {
        "tests/src/**/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt",
        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemFactoryTest.kt",
        "tests/src/**/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt",
        // TODO(b/322324387): Fails to start due to missing ScreenshotActivity
        "tests/src/**/systemui/bouncer/ui/composable/BouncerContentTest.kt",
        "tests/src/**/systemui/bouncer/ui/composable/PatternBouncerTest.kt",
        "tests/src/**/systemui/broadcast/UserBroadcastDispatcherTest.kt",
        "tests/src/**/systemui/charging/WiredChargingRippleControllerTest.kt",
        "tests/src/**/systemui/clipboardoverlay/ClipboardModelTest.kt",
@@ -487,6 +490,8 @@ android_library {
        "SystemUI-res",
        "WifiTrackerLib",
        "PlatformAnimationLib",
        "PlatformMotionTestingCompose",
        "ScreenshotComposeUtilsLib",
        "SystemUIPluginLib",
        "SystemUISharedLib",
        "SystemUICustomizationLib",
+1 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ android_library {

    static_libs: [
        "PlatformAnimationLib",
        "PlatformMotionTestingComposeValues",

        "androidx.compose.runtime_runtime",
        "androidx.compose.material3_material3",
+53 −18
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ package com.android.systemui.bouncer.ui.composable

import android.app.AlertDialog
import android.content.DialogInterface
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState
@@ -69,6 +70,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
@@ -104,6 +106,9 @@ import com.android.systemui.res.R
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.pow
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.MotionTestValues
import platform.test.motion.compose.values.motionTestValues

@Composable
fun BouncerContent(
@@ -114,6 +119,17 @@ fun BouncerContent(
    val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsStateWithLifecycle()
    val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported)

    BouncerContent(layout, viewModel, dialogFactory, modifier)
}

@Composable
@VisibleForTesting
fun BouncerContent(
    layout: BouncerSceneLayout,
    viewModel: BouncerViewModel,
    dialogFactory: BouncerDialogFactory,
    modifier: Modifier
) {
    Box(
        // Allows the content within each of the layouts to react to the appearance and
        // disappearance of the IME, which is also known as the software keyboard.
@@ -318,6 +334,8 @@ private fun BesideUserSwitcherLayout(
        LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
    val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()

    var swapAnimationEnd by remember { mutableStateOf(false) }

    Row(
        modifier =
            modifier
@@ -331,11 +349,16 @@ private fun BesideUserSwitcherLayout(
                        }
                    )
                }
                .testTag("BesideUserSwitcherLayout")
                .motionTestValues {
                    swapAnimationEnd exportAs BouncerMotionTestKeys.swapAnimationEnd
                }
                .padding(
                    top = if (isHeightExpanded) 128.dp else 96.dp,
                    bottom = if (isHeightExpanded) 128.dp else 48.dp,
                ),
    ) {
        LaunchedEffect(isSwapped) { swapAnimationEnd = false }
        val animatedOffset by
            animateFloatAsState(
                targetValue =
@@ -354,7 +377,9 @@ private fun BesideUserSwitcherLayout(
                        -1f
                    },
                label = "offset",
            )
            ) {
                swapAnimationEnd = true
            }

        fun Modifier.swappable(inversed: Boolean = false): Modifier {
            return graphicsLayer {
@@ -362,23 +387,25 @@ private fun BesideUserSwitcherLayout(
                        size.width *
                            animatedOffset *
                            if (inversed) {
                            // A negative sign is used to make sure this is offset in the direction
                            // that's opposite to the direction that the user switcher is pushed in.
                                // A negative sign is used to make sure this is offset in the
                                // direction that's opposite to the direction that the user
                                // switcher is pushed in.
                                -1
                            } else {
                                1
                            }
                    alpha = animatedAlpha(animatedOffset)
                }
                .motionTestValues { animatedAlpha(animatedOffset) exportAs MotionTestValues.alpha }
        }

        UserSwitcher(
            viewModel = viewModel,
            modifier = Modifier.weight(1f).swappable(),
            modifier = Modifier.weight(1f).swappable().testTag("UserSwitcher"),
        )

        FoldAware(
            modifier = Modifier.weight(1f).swappable(inversed = true),
            modifier = Modifier.weight(1f).swappable(inversed = true).testTag("FoldAware"),
            viewModel = viewModel,
            aboveFold = {
                Column(
@@ -389,7 +416,10 @@ private fun BesideUserSwitcherLayout(
                        viewModel = viewModel.message,
                    )

                    OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp))
                    OutputArea(
                        viewModel = viewModel,
                        modifier = Modifier.padding(top = 24.dp).testTag("OutputArea")
                    )
                }
            },
            belowFold = {
@@ -412,13 +442,13 @@ private fun BesideUserSwitcherLayout(
                            viewModel = viewModel,
                            pinButtonRowVerticalSpacing = 12.dp,
                            centerPatternDotsVertically = true,
                            modifier = Modifier.align(Alignment.BottomCenter),
                            modifier = Modifier.align(Alignment.BottomCenter).testTag("InputArea"),
                        )
                    }

                    ActionArea(
                        viewModel = viewModel,
                        modifier = Modifier.padding(top = 48.dp),
                        modifier = Modifier.padding(top = 48.dp).testTag("ActionArea"),
                    )
                }
            },
@@ -938,3 +968,8 @@ private object SceneElements {
private val SceneTransitions = transitions {
    from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() }
}

@VisibleForTesting
object BouncerMotionTestKeys {
    val swapAnimationEnd = MotionTestValueKey<Boolean>("swapAnimationEnd")
}
+61 −27
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.bouncer.ui.composable

import android.view.HapticFeedbackConstants
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.tween
@@ -50,6 +51,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Easings
import com.android.compose.modifiers.thenIf
import com.android.internal.R
import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotAppearFadeIn
import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotAppearMoveUp
import com.android.systemui.bouncer.ui.composable.MotionTestKeys.dotScaling
import com.android.systemui.bouncer.ui.composable.MotionTestKeys.entryCompleted
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -58,6 +63,8 @@ import kotlin.math.pow
import kotlin.math.sqrt
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.motionTestValues

/**
 * UI for the input part of a pattern-requiring version of the bouncer.
@@ -68,7 +75,8 @@ import kotlinx.coroutines.launch
 * `false`, the dots will be pushed towards the end/bottom of the axis.
 */
@Composable
internal fun PatternBouncer(
@VisibleForTesting
fun PatternBouncer(
    viewModel: PatternBouncerViewModel,
    centerDotsVertically: Boolean,
    modifier: Modifier = Modifier,
@@ -111,33 +119,11 @@ internal fun PatternBouncer(
        remember(dots) {
            dots.associateWith { dot -> with(density) { (80 + (20 * dot.y)).dp.toPx() } }
        }

    var entryAnimationCompleted by remember { mutableStateOf(false) }
    LaunchedEffect(Unit) {
        dotAppearFadeInAnimatables.forEach { (dot, animatable) ->
            scope.launch {
                animatable.animateTo(
                    targetValue = 1f,
                    animationSpec =
                        tween(
                            delayMillis = 33 * dot.y,
                            durationMillis = 450,
                            easing = Easings.LegacyDecelerate,
                        )
                )
            }
        }
        dotAppearMoveUpAnimatables.forEach { (dot, animatable) ->
            scope.launch {
                animatable.animateTo(
                    targetValue = 1f,
                    animationSpec =
                        tween(
                            delayMillis = 0,
                            durationMillis = 450 + (33 * dot.y),
                            easing = Easings.StandardDecelerate,
                        )
                )
            }
        }
        showEntryAnimation(dotAppearFadeInAnimatables, dotAppearMoveUpAnimatables)
        entryAnimationCompleted = true
    }

    val view = LocalView.current
@@ -286,6 +272,12 @@ internal fun PatternBouncer(
                        }
                    }
            }
            .motionTestValues {
                entryAnimationCompleted exportAs entryCompleted
                dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
                dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
                dotScalingAnimatables.map { it.value.value } exportAs dotScaling
            }
    ) {
        gridCoordinates?.let { nonNullCoordinates ->
            val containerSize = nonNullCoordinates.size
@@ -381,6 +373,40 @@ internal fun PatternBouncer(
    }
}

private suspend fun showEntryAnimation(
    dotAppearFadeInAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>,
    dotAppearMoveUpAnimatables: Map<PatternDotViewModel, Animatable<Float, AnimationVector1D>>,
) {
    coroutineScope {
        dotAppearFadeInAnimatables.forEach { (dot, animatable) ->
            launch {
                animatable.animateTo(
                    targetValue = 1f,
                    animationSpec =
                        tween(
                            delayMillis = 33 * dot.y,
                            durationMillis = 450,
                            easing = Easings.LegacyDecelerate,
                        )
                )
            }
        }
        dotAppearMoveUpAnimatables.forEach { (dot, animatable) ->
            launch {
                animatable.animateTo(
                    targetValue = 1f,
                    animationSpec =
                        tween(
                            delayMillis = 0,
                            durationMillis = 450 + (33 * dot.y),
                            easing = Easings.StandardDecelerate,
                        )
                )
            }
        }
    }
}

/** Returns an [Offset] representation of the given [dot], in pixel coordinates. */
private fun pixelOffset(
    dot: PatternDotViewModel,
@@ -490,3 +516,11 @@ private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f).
private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
private const val FAILURE_ANIMATION_DOT_REVERT_ANIMATION_DURATION = 617

@VisibleForTesting
object MotionTestKeys {
    val entryCompleted = MotionTestValueKey<Boolean>("PinBouncer::entryAnimationCompleted")
    val dotAppearFadeIn = MotionTestValueKey<List<Float>>("PinBouncer::dotAppearFadeIn")
    val dotAppearMoveUp = MotionTestValueKey<List<Float>>("PinBouncer::dotAppearMoveUp")
    val dotScaling = MotionTestValueKey<List<Float>>("PinBouncer::dotScaling")
}
+3 −3
Original line number Diff line number Diff line
@@ -25,8 +25,8 @@ package {
android_test {
    name: "PlatformComposeSceneTransitionLayoutTests",
    manifest: "AndroidManifest.xml",
    defaults: ["MotionTestDefaults"],
    test_suites: ["device-tests"],
    certificate: "platform",

    srcs: [
        "src/**/*.kt",
@@ -38,7 +38,7 @@ android_test {

    static_libs: [
        "PlatformComposeSceneTransitionLayoutTestsUtils",

        "PlatformMotionTestingCompose",
        "androidx.test.runner",
        "androidx.test.ext.junit",

@@ -48,7 +48,7 @@ android_test {

        "truth",
    ],

    asset_dirs: ["goldens"],
    kotlincflags: ["-Xjvm-default=all"],
    use_resource_processor: true,
}
Loading