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

Commit 774158ba authored by Mike Schneider's avatar Mike Schneider Committed by Android (Google) Code Review
Browse files

Merge "Few initial motion tests for both, STL and Flexiglass" into main

parents 1107f93b c776b93f
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -193,6 +193,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",
@@ -533,6 +536,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