Loading packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt +7 −1 Original line number Original line Diff line number Diff line Loading @@ -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. */ Loading @@ -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 } packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt +59 −19 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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 }, Loading @@ -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 = Loading @@ -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 Loading @@ -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 } ), ) ) } } } } Loading @@ -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") } packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +37 −2 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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, Loading @@ -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] Loading Loading @@ -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") } packages/SystemUI/multivalentTests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt +6 −5 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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), ) ) } } packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt +6 −0 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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() Loading @@ -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 Loading
packages/SystemUI/compose/features/src/com/android/systemui/compose/modifiers/SysuiTestTag.kt +7 −1 Original line number Original line Diff line number Diff line Loading @@ -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. */ Loading @@ -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 }
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/mediaoutput/ui/composable/MediaOutputComponent.kt +59 −19 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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) { Loading Loading @@ -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 }, Loading @@ -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 = Loading @@ -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 Loading @@ -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 } ), ) ) } } } } Loading @@ -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") }
packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/ColumnVolumeSliders.kt +37 −2 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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, Loading @@ -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, Loading @@ -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] Loading Loading @@ -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") }
packages/SystemUI/multivalentTests/src/com/android/systemui/motion/ComposeMotionTestRuleHelper.kt +6 −5 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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), ) ) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaDeviceSessionInteractorTest.kt +6 −0 Original line number Original line Diff line number Diff line Loading @@ -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 Loading @@ -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) Loading @@ -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() Loading @@ -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