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

Commit 9ae7d6f4 authored by Johannes Gallmann's avatar Johannes Gallmann
Browse files

[Floaty] Add MotionTest for Floaty effect

Bug: 413627757
Flag: com.android.systemui.shared.enable_lpp_assist_invocation_effect
Test: SqueezeEffectMotionTest
Change-Id: I7f434326cd113f8d8bf031a1f1b6e8312cf35ff5
parent a3352e31
Loading
Loading
Loading
Loading
+129 −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.systemui.topwindoweffects.ui.compose

import android.platform.test.annotations.MotionTest
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.topwindoweffects.data.repository.SqueezeEffectRepositoryImpl
import com.android.systemui.topwindoweffects.ui.viewmodel.squeezeEffectViewModelFactory
import com.android.wm.shell.appzoomout.appZoomOutOptional
import com.android.wm.shell.appzoomout.fakeAppZoomOut
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import platform.test.motion.compose.ComposeRecordingSpec
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.DataPointTypes
import platform.test.motion.golden.FeatureCapture
import platform.test.motion.golden.asDataPoint

@RunWith(AndroidJUnit4::class)
@LargeTest
@MotionTest
class SqueezeEffectMotionTest : SysuiTestCase() {
    private val kosmos = testKosmos()

    @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos)

    @DrawableRes private val topResId = R.drawable.rounded_corner_top
    @DrawableRes private val bottomResId = R.drawable.rounded_corner_bottom

    @Composable
    private fun SqueezeEffectUnderTest(onEffectFinished: () -> Unit) {
        SqueezeEffect(
            viewModelFactory = kosmos.squeezeEffectViewModelFactory,
            topRoundedCornerResourceId = topResId,
            bottomRoundedCornerResourceId = bottomResId,
            physicalPixelDisplaySizeRatio = 1f,
            onEffectFinished = onEffectFinished,
            appZoomOutOptional = kosmos.appZoomOutOptional,
            interactionJankMonitor = kosmos.interactionJankMonitor,
            onEffectStarted = {},
        )
    }

    @Test
    fun testSqueezeEffectMotion() =
        motionTestRule.runTest(timeout = 30.seconds) {
            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
            kosmos.fakeKeyEventRepository.setPowerButtonLongPressed(true)
            var effectFinished = false
            val motion =
                recordMotion(
                    content = { play ->
                        if (play) {
                            SqueezeEffectUnderTest(onEffectFinished = { effectFinished = true })
                        }
                    },
                    ComposeRecordingSpec.until(checkDone = { effectFinished }) {
                        feature(
                            FeatureCapture("topLevelZoom") {
                                kosmos.fakeAppZoomOut.lastTopLevelScale.asDataPoint()
                            }
                        )
                        feature(MotionTestKeys.squeezeThickness, DataPointTypes.float)
                    },
                )
            assertThat(motion).timeSeriesMatchesGolden()
        }

    @Test
    fun testSqueezeEffectCancelMotion() =
        motionTestRule.runTest(timeout = 30.seconds) {
            kosmos.fakeKeyEventRepository.setPowerButtonDown(true)
            kosmos.fakeKeyEventRepository.setPowerButtonLongPressed(false)
            var effectFinished = false

            val motionControl = MotionControl {
                awaitDelay(SqueezeEffectRepositoryImpl.DEFAULT_INITIAL_DELAY_MILLIS.milliseconds)
                kosmos.fakeKeyEventRepository.setPowerButtonDown(false)
                awaitCondition { effectFinished }
            }

            val motion =
                recordMotion(
                    content = { play ->
                        if (play) {
                            SqueezeEffectUnderTest(onEffectFinished = { effectFinished = true })
                        }
                    },
                    ComposeRecordingSpec(motionControl = motionControl) {
                        feature(
                            FeatureCapture("topLevelZoom") {
                                kosmos.fakeAppZoomOut.lastTopLevelScale.asDataPoint()
                            }
                        )
                        feature(MotionTestKeys.squeezeThickness, DataPointTypes.float)
                    },
                )
            assertThat(motion).timeSeriesMatchesGolden()
        }
}
+19 −3
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.topwindoweffects.ui.compose

import androidx.annotation.DrawableRes
import androidx.annotation.VisibleForTesting
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
@@ -50,6 +51,8 @@ import com.android.systemui.topwindoweffects.ui.viewmodel.SqueezeEffectViewModel
import com.android.wm.shell.appzoomout.AppZoomOut
import java.util.Optional
import kotlin.math.max
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.motionTestValues

private val SqueezeColor = Color.Black
private val SqueezeEffectMaxThickness = 16.dp
@@ -140,11 +143,12 @@ fun SqueezeEffect(
        }
    }

    val density = LocalDensity.current
    val screenWidthPx = LocalWindowInfo.current.containerSize.width
    val screenHeightPx = LocalWindowInfo.current.containerSize.height
    val longEdgePx = max(screenHeightPx, screenWidthPx)
    val zoomPotentialPx =
        with(LocalDensity.current) {
        with(density) {
            (SqueezeEffectMaxThickness.toPx() - SqueezeEffectOverlapShortEdgeThickness.toPx()) * 2
        }
    val zoomOutScale = 1f - (longEdgePx - zoomPotentialPx) / longEdgePx
@@ -155,11 +159,18 @@ fun SqueezeEffect(
        }
    }

    Canvas(modifier = Modifier.fillMaxSize()) {
    val squeezeThickness =
        with(density) { SqueezeEffectMaxThickness.toPx() * squeezeProgress.value }

    Canvas(
        modifier =
            Modifier.fillMaxSize().motionTestValues {
                squeezeThickness exportAs MotionTestKeys.squeezeThickness
            }
    ) {
        if (squeezeProgress.value <= 0) {
            return@Canvas
        }
        val squeezeThickness = SqueezeEffectMaxThickness.toPx() * squeezeProgress.value

        drawRect(color = SqueezeColor, size = Size(size.width, squeezeThickness))

@@ -242,3 +253,8 @@ private fun DrawScope.drawTransform(
        }
    }
}

@VisibleForTesting
object MotionTestKeys {
    val squeezeThickness = MotionTestValueKey<Float>("squeezeThickness")
}
+128 −0
Original line number Diff line number Diff line
{
  "frame_ids": [
    "before",
    0,
    16,
    32,
    48,
    64,
    80,
    96,
    112,
    128,
    144,
    160,
    176,
    192,
    208,
    224,
    240,
    256,
    272,
    288,
    304,
    320,
    336,
    352,
    368,
    384,
    400,
    416,
    432,
    448,
    464,
    480,
    496,
    512,
    528,
    "after"
  ],
  "features": [
    {
      "name": "topLevelZoom",
      "type": "float",
      "data_points": [
        1,
        1,
        1,
        1,
        0.99997663,
        0.99990267,
        0.99977136,
        0.9995748,
        0.9993037,
        0.9989471,
        0.99849236,
        0.99792534,
        0.99723065,
        0.99639374,
        0.99639374,
        0.9964129,
        0.9964791,
        0.9966101,
        0.99683064,
        0.9971648,
        0.9976032,
        0.998071,
        0.9984877,
        0.9988274,
        0.9990975,
        0.9993119,
        0.9994826,
        0.99961853,
        0.9997264,
        0.99981135,
        0.99987704,
        0.9999264,
        0.9999617,
        0.99998474,
        0.999997,
        1
      ]
    },
    {
      "name": "squeezeThickness",
      "type": "float",
      "data_points": [
        {
          "type": "not_found"
        },
        0,
        0,
        0,
        0.0485566,
        0.20243222,
        0.47556558,
        0.8844038,
        1.4483277,
        2.1900382,
        3.135834,
        4.3153386,
        5.760244,
        7.5010285,
        7.5010285,
        7.461228,
        7.323543,
        7.050966,
        6.592264,
        5.8971934,
        4.9853473,
        4.012273,
        3.145575,
        2.4389622,
        1.8771063,
        1.4311807,
        1.0762641,
        0.7935125,
        0.5689978,
        0.3923689,
        0.25576895,
        0.15314473,
        0.07974452,
        0.031769313,
        0.006137736,
        0
      ]
    }
  ]
}
 No newline at end of file
+248 −0
Original line number Diff line number Diff line
{
  "frame_ids": [
    "before",
    0,
    16,
    32,
    48,
    64,
    80,
    96,
    112,
    128,
    144,
    160,
    176,
    192,
    208,
    224,
    240,
    256,
    272,
    288,
    304,
    320,
    336,
    352,
    368,
    384,
    400,
    416,
    432,
    448,
    464,
    480,
    496,
    512,
    528,
    544,
    560,
    576,
    592,
    608,
    624,
    640,
    656,
    672,
    688,
    704,
    720,
    736,
    752,
    768,
    784,
    800,
    816,
    832,
    848,
    864,
    880,
    896,
    912,
    928,
    944,
    960,
    976,
    992,
    1008,
    1024,
    1040,
    1056,
    1072,
    1088,
    1104,
    1120,
    1136,
    1152,
    1168,
    "after"
  ],
  "features": [
    {
      "name": "topLevelZoom",
      "type": "float",
      "data_points": [
        1,
        1,
        1,
        1,
        0.99997663,
        0.99990267,
        0.99977136,
        0.9995748,
        0.9993037,
        0.9989471,
        0.99849236,
        0.99792534,
        0.99723065,
        0.99639374,
        0.99540365,
        0.9942581,
        0.99297,
        0.9915714,
        0.9901113,
        0.9886452,
        0.9872224,
        0.985877,
        0.9846275,
        0.98348016,
        0.9824334,
        0.98148155,
        0.9806171,
        0.9798323,
        0.9791195,
        0.9784718,
        0.97788286,
        0.97734714,
        0.9768597,
        0.9764163,
        0.97601295,
        0.97564644,
        0.9753137,
        0.97501224,
        0.97473955,
        0.9744936,
        0.9742726,
        0.9740748,
        0.97389865,
        0.9737428,
        0.9736061,
        0.9734873,
        0.97338545,
        0.9732996,
        0.97322893,
        0.9731726,
        0.97312987,
        0.97310007,
        0.9730826,
        0.97307694,
        0.97307694,
        0.9732198,
        0.973714,
        0.97469234,
        0.97633874,
        0.9788335,
        0.9821063,
        0.985599,
        0.98870975,
        0.991246,
        0.9932626,
        0.99486315,
        0.996137,
        0.9971519,
        0.9979577,
        0.99859166,
        0.99908197,
        0.9994503,
        0.9997138,
        0.999886,
        0.99997795,
        1
      ]
    },
    {
      "name": "squeezeThickness",
      "type": "float",
      "data_points": [
        {
          "type": "not_found"
        },
        0,
        0,
        0,
        0.0485566,
        0.20243222,
        0.47556558,
        0.8844038,
        1.4483277,
        2.1900382,
        3.135834,
        4.3153386,
        5.760244,
        7.5010285,
        9.560463,
        11.943141,
        14.622387,
        17.531492,
        20.568546,
        23.617954,
        26.577538,
        29.37596,
        31.974844,
        34.361305,
        36.53857,
        38.518456,
        40.31645,
        41.94887,
        43.43146,
        44.77871,
        46.00368,
        47.11797,
        48.131817,
        49.054203,
        49.89308,
        50.655434,
        51.347454,
        51.974613,
        52.541767,
        53.053272,
        53.51303,
        53.924522,
        54.290886,
        54.61499,
        54.899387,
        55.14641,
        55.35824,
        55.53677,
        55.683823,
        55.801037,
        55.889915,
        55.951866,
        55.988163,
        56,
        56,
        55.70286,
        54.674953,
        52.63999,
        49.21549,
        44.02634,
        37.218822,
        29.954193,
        23.483738,
        18.20842,
        14.013805,
        10.684684,
        8.035004,
        5.924081,
        4.2479343,
        2.929286,
        1.9094796,
        1.1433239,
        0.59534407,
        0.23717833,
        0.045822144,
        0
      ]
    }
  ]
}
 No newline at end of file
+36 −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.systemui.topwindoweffects.ui.viewmodel

import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture

val Kosmos.squeezeEffectViewModel by Fixture {
    SqueezeEffectViewModel(
        keyEventInteractor = keyEventInteractor,
        squeezeEffectHapticPlayerFactory = squeezeEffectHapticPlayerFactory,
    )
}

val Kosmos.squeezeEffectViewModelFactory by Fixture {
    object : SqueezeEffectViewModel.Factory {
        override fun create(): SqueezeEffectViewModel {
            return squeezeEffectViewModel
        }
    }
}
Loading