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

Commit 6b5333d4 authored by Sandeep Suman's avatar Sandeep Suman Committed by Android (Google) Code Review
Browse files

Merge "Fixed test for volume sliders and media output component" into main

parents d4c52bd9 d1a06ac0
Loading
Loading
Loading
Loading
+7 −1
Original line number Original line Diff line number Diff line
@@ -32,7 +32,7 @@ import androidx.compose.ui.semantics.testTagsAsResourceId
 */
 */
@Stable
@Stable
fun Modifier.sysuiResTag(resId: String): Modifier {
fun Modifier.sysuiResTag(resId: String): Modifier {
    return this.testTag("com.android.systemui:id/$resId")
    return this.testTag(resIdToTestTag(resId))
}
}


/** Mark this node as a container that contains one or more [sysuiResTag] descendants. */
/** Mark this node as a container that contains one or more [sysuiResTag] descendants. */
@@ -41,4 +41,10 @@ fun Modifier.sysUiResTagContainer(): Modifier {
    return this.then(TestTagAsResourceIdModifier)
    return this.then(TestTagAsResourceIdModifier)
}
}


/**
 * Converts a simple resource ID name string into a fully qualified resource name string, formatted
 * for use as a test tag within the Android SystemUI package.
 */
fun resIdToTestTag(resId: String): String = "com.android.systemui:id/$resId"

private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
private val TestTagAsResourceIdModifier = Modifier.semantics { testTagsAsResourceId = true }
+59 −19
Original line number Original line Diff line number Diff line
@@ -45,7 +45,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.stringResource
@@ -58,6 +60,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
import com.android.compose.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.toColor
import com.android.systemui.common.ui.compose.toColor
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.ConnectedDeviceViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.DeviceIconViewModel
@@ -65,14 +68,15 @@ import com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel.Medi
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
import com.google.common.annotations.VisibleForTesting
import java.util.Objects
import javax.inject.Inject
import javax.inject.Inject
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.motionTestValues


@VolumePanelScope
@VolumePanelScope
class MediaOutputComponent
class MediaOutputComponent @Inject constructor(private val viewModel: MediaOutputViewModel) :
@Inject
    ComposeVolumePanelUiComponent {
constructor(
    private val viewModel: MediaOutputViewModel,
) : ComposeVolumePanelUiComponent {


    @Composable
    @Composable
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
    override fun VolumePanelComposeScope.Content(modifier: Modifier) {
@@ -142,9 +146,19 @@ constructor(
    @Composable
    @Composable
    private fun ConnectedDeviceIcon(deviceIconViewModel: DeviceIconViewModel) {
    private fun ConnectedDeviceIcon(deviceIconViewModel: DeviceIconViewModel) {
        val transition = updateTransition(deviceIconViewModel, label = "MediaOutputIconTransition")
        val transition = updateTransition(deviceIconViewModel, label = "MediaOutputIconTransition")
        val isTransitionIdle by
            remember(transition) {
                derivedStateOf {
                    transition.currentState == transition.targetState && !transition.isRunning
                }
            }
        Box(
        Box(
            modifier = Modifier.padding(16.dp).fillMaxHeight().aspectRatio(1f),
            modifier =
            contentAlignment = Alignment.Center
                Modifier.padding(16.dp).fillMaxHeight().aspectRatio(1f).motionTestValues {
                    isTransitionIdle exportAs
                        MediaOutputComponentMotionTestKeys.isIconTransitionIdle
                },
            contentAlignment = Alignment.Center,
        ) {
        ) {
            transition.AnimatedContent(
            transition.AnimatedContent(
                contentKey = { it.backgroundColor },
                contentKey = { it.backgroundColor },
@@ -157,12 +171,10 @@ constructor(
                            fadeOut(animationSpec = snap())
                            fadeOut(animationSpec = snap())
                    } else {
                    } else {
                        fadeIn(animationSpec = snap(delayMillis = 900)) togetherWith
                        fadeIn(animationSpec = snap(delayMillis = 900)) togetherWith
                            scaleOut(
                            scaleOut(targetScale = 0.9f, animationSpec = isPlayingOutSpec()) +
                                targetScale = 0.9f,
                                fadeOut(animationSpec = isPlayingOutSpec())
                                animationSpec = isPlayingOutSpec(),
                            ) + fadeOut(animationSpec = isPlayingOutSpec())
                    }
                    }
                    }
                },
            ) { targetViewModel ->
            ) { targetViewModel ->
                Spacer(
                Spacer(
                    modifier =
                    modifier =
@@ -170,11 +182,18 @@ constructor(
                            .background(
                            .background(
                                color = targetViewModel.backgroundColor.toColor(),
                                color = targetViewModel.backgroundColor.toColor(),
                                shape = RoundedCornerShape(12.dp),
                                shape = RoundedCornerShape(12.dp),
                            ),
                            )
                            .sysuiResTag(
                                if (targetViewModel is DeviceIconViewModel.IsPlaying) {
                                    MediaOutputComponentMotionTestKeys.PLAYING_ICON_BACKGROUND_TAG
                                } else {
                                    MediaOutputComponentMotionTestKeys.IDLE_ICON_BACKGROUND_TAG
                                }
                            )
                )
                )
            }
            }
            transition.AnimatedContent(
            transition.AnimatedContent(
                contentKey = { it.icon },
                contentKey = { Objects.hash(it.icon, it.iconColor) },
                transitionSpec = {
                transitionSpec = {
                    if (targetState is DeviceIconViewModel.IsPlaying) {
                    if (targetState is DeviceIconViewModel.IsPlaying) {
                        fadeIn(animationSpec = snap(delayMillis = 700)) togetherWith
                        fadeIn(animationSpec = snap(delayMillis = 700)) togetherWith
@@ -189,12 +208,21 @@ constructor(
                        ) + fadeIn(animationSpec = isNotPlayingInIconSpec()) togetherWith
                        ) + fadeIn(animationSpec = isNotPlayingInIconSpec()) togetherWith
                            fadeOut(animationSpec = isPlayingOutSpec())
                            fadeOut(animationSpec = isPlayingOutSpec())
                    }
                    }
                }
                },
            ) {
            ) { targetViewModel ->
                Icon(
                Icon(
                    icon = it.icon,
                    icon = targetViewModel.icon,
                    tint = it.iconColor.toColor(),
                    tint = targetViewModel.iconColor.toColor(),
                    modifier = Modifier.padding(12.dp).fillMaxSize(),
                    modifier =
                        Modifier.padding(12.dp)
                            .fillMaxSize()
                            .sysuiResTag(
                                if (targetViewModel is DeviceIconViewModel.IsPlaying) {
                                    MediaOutputComponentMotionTestKeys.PLAYING_ICON_TAG
                                } else {
                                    MediaOutputComponentMotionTestKeys.IDLE_ICON_TAG
                                }
                            ),
                )
                )
            }
            }
        }
        }
@@ -210,3 +238,15 @@ private fun <T> isPlayingInIconBackgroundSpec() = tween<T>(durationMillis = 400,
private fun <T> isNotPlayingOutIconSpec() = tween<T>(durationMillis = 400, delayMillis = 300)
private fun <T> isNotPlayingOutIconSpec() = tween<T>(durationMillis = 400, delayMillis = 300)


private fun <T> isNotPlayingInIconSpec() = tween<T>(durationMillis = 400, delayMillis = 900)
private fun <T> isNotPlayingInIconSpec() = tween<T>(durationMillis = 400, delayMillis = 900)

@VisibleForTesting
object MediaOutputComponentMotionTestKeys {

    const val PLAYING_ICON_TAG = "PlayingIcon"
    const val PLAYING_ICON_BACKGROUND_TAG = "PlayingIconBackground"
    const val IDLE_ICON_TAG = "IdleIcon"
    const val IDLE_ICON_BACKGROUND_TAG = "IdleIconBackground"

    val isIconTransitionIdle: MotionTestValueKey<Boolean> =
        MotionTestValueKey("is_icon_transition_idle")
}
+37 −2
Original line number Original line Diff line number Diff line
@@ -41,7 +41,9 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.painterResource
@@ -58,8 +60,12 @@ import com.android.compose.PlatformSliderColors
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
import com.android.compose.modifiers.thenIf
import com.android.systemui.Flags
import com.android.systemui.Flags
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.res.R
import com.android.systemui.res.R
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
import com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel.SliderViewModel
import com.google.common.annotations.VisibleForTesting
import platform.test.motion.compose.values.MotionTestValueKey
import platform.test.motion.compose.values.motionTestValues


private const val EXPAND_DURATION_MILLIS = 500
private const val EXPAND_DURATION_MILLIS = 500
private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350
private const val COLLAPSE_EXPAND_BUTTON_DELAY_MILLIS = 350
@@ -104,6 +110,11 @@ fun ColumnVolumeSliders(
                    if (Flags.volumeRedesign()) {
                    if (Flags.volumeRedesign()) {
                        {
                        {
                            ExpandButton(
                            ExpandButton(
                                modifier =
                                    Modifier.sysuiResTag(
                                        ColumnVolumeSlidersMotionTestKeys
                                            .TOGGLE_EXPANSION_BUTTON_TAG
                                    ),
                                isExpanded = isExpanded,
                                isExpanded = isExpanded,
                                isExpandable = isExpandable,
                                isExpandable = isExpandable,
                                onExpandedChanged = onExpandedChanged,
                                onExpandedChanged = onExpandedChanged,
@@ -116,7 +127,11 @@ fun ColumnVolumeSliders(


            if (!Flags.volumeRedesign()) {
            if (!Flags.volumeRedesign()) {
                ExpandButtonLegacy(
                ExpandButtonLegacy(
                    modifier = Modifier.align(Alignment.CenterEnd),
                    modifier =
                        Modifier.align(Alignment.CenterEnd)
                            .sysuiResTag(
                                ColumnVolumeSlidersMotionTestKeys.TOGGLE_EXPANSION_BUTTON_TAG
                            ),
                    isExpanded = isExpanded,
                    isExpanded = isExpanded,
                    isExpandable = isExpandable,
                    isExpandable = isExpandable,
                    onExpandedChanged = onExpandedChanged,
                    onExpandedChanged = onExpandedChanged,
@@ -132,9 +147,22 @@ fun ColumnVolumeSliders(
            exit =
            exit =
                shrinkVertically(animationSpec = tween(durationMillis = COLLAPSE_DURATION_MILLIS)),
                shrinkVertically(animationSpec = tween(durationMillis = COLLAPSE_DURATION_MILLIS)),
        ) {
        ) {
            val isTransitionIdle by
                remember(transition) {
                    derivedStateOf {
                        transition.currentState == transition.targetState && !transition.isRunning
                    }
                }
            // This box allows sliders to slide towards top when the container is shrinking and
            // This box allows sliders to slide towards top when the container is shrinking and
            // slide from top when the container is expanding.
            // slide from top when the container is expanding.
            Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomCenter) {
            Box(
                modifier =
                    Modifier.fillMaxWidth().motionTestValues {
                        isTransitionIdle exportAs
                            ColumnVolumeSlidersMotionTestKeys.isSlidersTransitionIdle
                    },
                contentAlignment = Alignment.BottomCenter,
            ) {
                Column {
                Column {
                    for (index in 1..viewModels.lastIndex) {
                    for (index in 1..viewModels.lastIndex) {
                        val sliderViewModel: SliderViewModel = viewModels[index]
                        val sliderViewModel: SliderViewModel = viewModels[index]
@@ -352,3 +380,10 @@ private fun topSliderPadding(isExpandable: Boolean): State<Dp> {
        label = "TopVolumeSliderPadding",
        label = "TopVolumeSliderPadding",
    )
    )
}
}

@VisibleForTesting
object ColumnVolumeSlidersMotionTestKeys {
    const val TOGGLE_EXPANSION_BUTTON_TAG = "sliders_toggle_button"
    val isSlidersTransitionIdle: MotionTestValueKey<Boolean> =
        MotionTestValueKey("is_sliders_transition_idle")
}
+6 −5
Original line number Original line Diff line number Diff line
@@ -13,14 +13,15 @@
 * See the License for the specific language governing permissions and
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * limitations under the License.
 */
 */

package com.android.systemui.motion
package com.android.systemui.motion


import androidx.compose.ui.unit.Density
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import org.junit.rules.RuleChain
import org.junit.rules.RuleChain
import platform.test.motion.MotionTestRule
import platform.test.motion.MotionTestRule
import platform.test.motion.compose.ComposeToolkit
import platform.test.motion.compose.ComposeToolkit
import platform.test.motion.compose.FixedConfiguration
import platform.test.motion.testing.createGoldenPathManager
import platform.test.motion.testing.createGoldenPathManager
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.DeviceEmulationSpec
import platform.test.screenshot.Displays
import platform.test.screenshot.Displays
@@ -36,14 +37,14 @@ fun createSysUiComposeMotionTestRule(
    val goldenPathManager =
    val goldenPathManager =
        createGoldenPathManager("frameworks/base/packages/SystemUI/tests/goldens", pathConfig)
        createGoldenPathManager("frameworks/base/packages/SystemUI/tests/goldens", pathConfig)
    val testScope = kosmos.testScope
    val testScope = kosmos.testScope

    val composeScreenshotTestRule =
    val composeScreenshotTestRule =
        ComposeScreenshotTestRule(deviceEmulationSpec, goldenPathManager)
        ComposeScreenshotTestRule(deviceEmulationSpec, goldenPathManager)

    val fixedConfiguration =
        FixedConfiguration(density = Density(deviceEmulationSpec.display.densityDpi / 160f))
    return MotionTestRule(
    return MotionTestRule(
        ComposeToolkit(composeScreenshotTestRule.composeRule, testScope),
        ComposeToolkit(composeScreenshotTestRule.composeRule, testScope, fixedConfiguration),
        goldenPathManager,
        goldenPathManager,
        bitmapDiffer = composeScreenshotTestRule,
        bitmapDiffer = composeScreenshotTestRule,
        extraRules = RuleChain.outerRule(composeScreenshotTestRule)
        extraRules = RuleChain.outerRule(composeScreenshotTestRule),
    )
    )
}
}
+6 −0
Original line number Original line Diff line number Diff line
@@ -24,6 +24,8 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.testKosmos
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localPlaybackInfo
import com.android.systemui.volume.localPlaybackStateBuilder
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaControllerRepository
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputInteractor
import com.android.systemui.volume.mediaOutputInteractor
@@ -35,6 +37,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.kotlin.whenever


@SmallTest
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
@@ -60,6 +63,7 @@ class MediaDeviceSessionInteractorTest : SysuiTestCase() {
    fun playbackInfo_returnsPlaybackInfo() {
    fun playbackInfo_returnsPlaybackInfo() {
        with(kosmos) {
        with(kosmos) {
            testScope.runTest {
            testScope.runTest {
                whenever(localMediaController.playbackInfo).thenReturn(localPlaybackInfo)
                val session by
                val session by
                    collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
                    collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
                runCurrent()
                runCurrent()
@@ -75,6 +79,8 @@ class MediaDeviceSessionInteractorTest : SysuiTestCase() {
    fun playbackState_returnsPlaybackState() {
    fun playbackState_returnsPlaybackState() {
        with(kosmos) {
        with(kosmos) {
            testScope.runTest {
            testScope.runTest {
                whenever(localMediaController.playbackState)
                    .thenReturn(localPlaybackStateBuilder.build())
                val session by
                val session by
                    collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
                    collectLastValue(mediaOutputInteractor.defaultActiveMediaSession.filterData())
                runCurrent()
                runCurrent()
Loading