Loading packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +8 −19 Original line number Original line Diff line number Diff line Loading @@ -149,9 +149,6 @@ fun ContentScope.CollapsedShadeHeader( } } } } val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() val isShadeLayoutWide = viewModel.isShadeLayoutWide val isShadeLayoutWide = viewModel.isShadeLayoutWide val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Loading @@ -167,8 +164,8 @@ fun ContentScope.CollapsedShadeHeader( ) { ) { Clock(scale = 1f, onClick = viewModel::onClockClicked) Clock(scale = 1f, onClick = viewModel::onClockClicked) VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), ) ) Loading Loading @@ -229,8 +226,6 @@ fun ContentScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } } val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { Loading Loading @@ -269,8 +264,8 @@ fun ContentScope.ExpandedShadeHeader( modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), ) { ) { VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.widthIn(max = 90.dp), modifier = Modifier.widthIn(max = 90.dp), ) ) Loading Loading @@ -337,12 +332,9 @@ fun ContentScope.OverlayShadeHeader( modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), ) ) } else { } else { val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, ) ) } } Loading Loading @@ -546,11 +538,8 @@ private fun BatteryIcon( @Composable @Composable private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { Row(modifier = modifier) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(5.dp)) { val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle() for (subId in viewModel.mobileSubIds) { for (subId in subIds) { Spacer(modifier = Modifier.width(5.dp)) AndroidView( AndroidView( factory = { context -> factory = { context -> ModernShadeCarrierGroupMobileView.constructAndBind( ModernShadeCarrierGroupMobileView.constructAndBind( Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt +85 −3 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.provider.AlarmClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback Loading @@ -31,22 +34,32 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import java.util.Date import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.ArgumentMatcher import org.mockito.ArgumentMatcher import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) class ShadeHeaderClockInteractorTest : SysuiTestCase() { class ShadeHeaderClockInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val activityStarter = kosmos.activityStarter private val activityStarter = kosmos.activityStarter private val nextAlarmController = kosmos.nextAlarmController private val nextAlarmController = kosmos.nextAlarmController val underTest = kosmos.shadeHeaderClockInteractor private val underTest = kosmos.shadeHeaderClockInteractor @Test @Test fun launchClockActivity_default() = fun launchClockActivity_default() = Loading @@ -55,7 +68,7 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { verify(activityStarter) verify(activityStarter) .postStartActivityDismissingKeyguard( .postStartActivityDismissingKeyguard( argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)), argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)), any() any(), ) ) } } Loading @@ -71,6 +84,75 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { underTest.launchClockActivity() underTest.launchClockActivity() verify(activityStarter).postStartActivityDismissingKeyguard(any()) verify(activityStarter).postStartActivityDismissingKeyguard(any()) } } @Test fun onTimezoneOrLocaleChanged_localeAndTimezoneChanged_emitsForEach() = testScope.runTest { val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) assertThat(timeZoneOrLocaleChanges).hasSize(4) } @Test fun onTimezoneOrLocaleChanged_timeChanged_doesNotEmit() = testScope.runTest { val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) assertThat(timeZoneOrLocaleChanges).hasSize(1) sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) // Expect only 1 event to have been emitted onStart, but no more. assertThat(timeZoneOrLocaleChanges).hasSize(1) } @Test fun currentTime_timeChanged() = testScope.runTest { val currentTime by collectLastValue(underTest.currentTime) sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) val earlierTime = checkNotNull(currentTime) advanceTimeBy(3.seconds) runCurrent() sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) val laterTime = checkNotNull(currentTime) assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(3.seconds) } @Test fun currentTime_timeTicked() = testScope.runTest { val currentTime by collectLastValue(underTest.currentTime) sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) val earlierTime = checkNotNull(currentTime) advanceTimeBy(7.seconds) runCurrent() sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) val laterTime = checkNotNull(currentTime) assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(7.seconds) } private fun differenceBetween(date1: Date, date2: Date): Duration { return (date1.time - date2.time).milliseconds } private fun TestScope.sendIntentActionBroadcast(intentAction: String) { kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, Intent(intentAction)) runCurrent() } } } private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> { private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> { Loading packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +54 −85 Original line number Original line Diff line number Diff line Loading @@ -25,12 +25,15 @@ import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSplitShade import com.android.systemui.shade.domain.interactor.enableSplitShade import com.android.systemui.shade.domain.interactor.shadeMode import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor import com.android.systemui.testKosmos import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argThat import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent Loading @@ -43,6 +46,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) @EnableSceneContainer @EnableSceneContainer Loading @@ -64,14 +68,15 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun mobileSubIds_update() = fun mobileSubIds_update() = testScope.runTest { testScope.runTest { val mobileSubIds by collectLastValue(underTest.mobileSubIds) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) runCurrent() assertThat(mobileSubIds).isEqualTo(listOf(1)) assertThat(underTest.mobileSubIds).isEqualTo(listOf(1)) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) runCurrent() assertThat(mobileSubIds).isEqualTo(listOf(1, 2)) assertThat(underTest.mobileSubIds).isEqualTo(listOf(1, 2)) } } @Test @Test Loading Loading @@ -116,13 +121,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -134,13 +135,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -166,13 +163,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -184,13 +177,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -203,13 +192,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -221,13 +206,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -240,13 +221,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -258,13 +235,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading Loading @@ -319,22 +292,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() = fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) setOverlay(Overlays.NotificationsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isNotEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) } } Loading @@ -342,22 +306,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() = fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) setOverlay(Overlays.QuickSettingsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isNotEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) } } Loading @@ -365,21 +320,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_noOverlaysInDualShade_bothNone() = fun highlightChips_noOverlaysInDualShade_bothNone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen) assertThat(currentOverlays).isEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone) setScene(Scenes.Gone) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) } } Loading @@ -401,21 +348,43 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { ) ) } } private fun setScene(key: SceneKey) { private fun TestScope.setupDualShadeState(scene: SceneKey, overlay: OverlayKey? = null) { sceneInteractor.changeScene(key, "test") kosmos.enableDualShade() sceneInteractor.setTransitionState( val shadeMode by collectLastValue(kosmos.shadeMode) MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) if (scene == Scenes.Gone) { // Unlock the device, marking the device has been entered. kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) ) testScope.runCurrent() } } runCurrent() assertThat(shadeMode).isEqualTo(ShadeMode.Dual) private fun setOverlay(key: OverlayKey) { sceneInteractor.changeScene(scene, "test") val currentOverlays = sceneInteractor.currentOverlays.value + key checkNotNull(currentOverlays).forEach { sceneInteractor.instantlyHideOverlay(it, "test") } sceneInteractor.showOverlay(key, "test") runCurrent() overlay?.let { sceneInteractor.showOverlay(it, "test") } sceneInteractor.setTransitionState( sceneInteractor.setTransitionState( MutableStateFlow<ObservableTransitionState>( MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays) ObservableTransitionState.Idle(scene, setOfNotNull(overlay)) ) ) ) runCurrent() assertThat(currentScene).isEqualTo(scene) if (overlay == null) { assertThat(currentOverlays).isEmpty() } else { assertThat(currentOverlays).containsExactly(overlay) } } private fun setScene(key: SceneKey) { sceneInteractor.changeScene(key, "test") sceneInteractor.setTransitionState( MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) ) ) testScope.runCurrent() testScope.runCurrent() } } Loading packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt +36 −1 Original line number Original line Diff line number Diff line Loading @@ -17,11 +17,19 @@ package com.android.systemui.shade.domain.interactor package com.android.systemui.shade.domain.interactor import android.content.Intent import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import android.provider.AlarmClock import android.provider.AlarmClock import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.time.SystemClock import java.util.Date import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @SysUISingleton @SysUISingleton class ShadeHeaderClockInteractor class ShadeHeaderClockInteractor Loading @@ -29,7 +37,20 @@ class ShadeHeaderClockInteractor constructor( constructor( private val repository: ShadeHeaderClockRepository, private val repository: ShadeHeaderClockRepository, private val activityStarter: ActivityStarter, private val activityStarter: ActivityStarter, private val broadcastDispatcher: BroadcastDispatcher, private val systemClock: SystemClock, ) { ) { /** [Flow] that emits `Unit` whenever the timezone or locale has changed. */ val onTimezoneOrLocaleChanged: Flow<Unit> = broadcastFlowForActions(Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_LOCALE_CHANGED) .emitOnStart() /** [Flow] that emits the current `Date` every minute, or when the system time has changed. */ val currentTime: Flow<Date> = broadcastFlowForActions(Intent.ACTION_TIME_TICK, Intent.ACTION_TIME_CHANGED) .emitOnStart() .map { Date(systemClock.currentTimeMillis()) } /** Launch the clock activity. */ /** Launch the clock activity. */ fun launchClockActivity() { fun launchClockActivity() { val nextAlarmIntent = repository.nextAlarmIntent val nextAlarmIntent = repository.nextAlarmIntent Loading @@ -38,8 +59,22 @@ constructor( } else { } else { activityStarter.postStartActivityDismissingKeyguard( activityStarter.postStartActivityDismissingKeyguard( Intent(AlarmClock.ACTION_SHOW_ALARMS), Intent(AlarmClock.ACTION_SHOW_ALARMS), 0 0, ) ) } } } } /** * Returns a `Flow` that, when collected, emits `Unit` whenever a broadcast matching one of the * given [actionsToFilter] is received. */ private fun broadcastFlowForActions( vararg actionsToFilter: String, user: UserHandle = UserHandle.SYSTEM, ): Flow<Unit> { return broadcastDispatcher.broadcastFlow( filter = IntentFilter().apply { actionsToFilter.forEach(::addAction) }, user = user, ) } } } packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +41 −65 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt +8 −19 Original line number Original line Diff line number Diff line Loading @@ -149,9 +149,6 @@ fun ContentScope.CollapsedShadeHeader( } } } } val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() val isShadeLayoutWide = viewModel.isShadeLayoutWide val isShadeLayoutWide = viewModel.isShadeLayoutWide val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Loading @@ -167,8 +164,8 @@ fun ContentScope.CollapsedShadeHeader( ) { ) { Clock(scale = 1f, onClick = viewModel::onClockClicked) Clock(scale = 1f, onClick = viewModel::onClockClicked) VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), modifier = Modifier.element(ShadeHeader.Elements.CollapsedContentStart), ) ) Loading Loading @@ -229,8 +226,6 @@ fun ContentScope.ExpandedShadeHeader( derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } derivedStateOf { shouldUseExpandedFormat(layoutState.transitionState) } } } val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() val isPrivacyChipVisible by viewModel.isPrivacyChipVisible.collectAsStateWithLifecycle() Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { Box(modifier = modifier.sysuiResTag(ShadeHeader.TestTags.Root)) { Loading Loading @@ -269,8 +264,8 @@ fun ContentScope.ExpandedShadeHeader( modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), modifier = Modifier.element(ShadeHeader.Elements.ExpandedContent), ) { ) { VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, modifier = Modifier.widthIn(max = 90.dp), modifier = Modifier.widthIn(max = 90.dp), ) ) Loading Loading @@ -337,12 +332,9 @@ fun ContentScope.OverlayShadeHeader( modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), modifier = Modifier.width(IntrinsicSize.Min).height(20.dp), ) ) } else { } else { val longerDateText by viewModel.longerDateText.collectAsStateWithLifecycle() val shorterDateText by viewModel.shorterDateText.collectAsStateWithLifecycle() VariableDayDate( VariableDayDate( longerDateText = longerDateText, longerDateText = viewModel.longerDateText, shorterDateText = shorterDateText, shorterDateText = viewModel.shorterDateText, chipHighlight = viewModel.notificationsChipHighlight, chipHighlight = viewModel.notificationsChipHighlight, ) ) } } Loading Loading @@ -546,11 +538,8 @@ private fun BatteryIcon( @Composable @Composable private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { private fun ShadeCarrierGroup(viewModel: ShadeHeaderViewModel, modifier: Modifier = Modifier) { Row(modifier = modifier) { Row(modifier = modifier, horizontalArrangement = Arrangement.spacedBy(5.dp)) { val subIds by viewModel.mobileSubIds.collectAsStateWithLifecycle() for (subId in viewModel.mobileSubIds) { for (subId in subIds) { Spacer(modifier = Modifier.width(5.dp)) AndroidView( AndroidView( factory = { context -> factory = { context -> ModernShadeCarrierGroupMobileView.constructAndBind( ModernShadeCarrierGroupMobileView.constructAndBind( Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractorTest.kt +85 −3 Original line number Original line Diff line number Diff line Loading @@ -22,6 +22,9 @@ import android.provider.AlarmClock import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.activityStarter import com.android.systemui.plugins.activityStarter import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback import com.android.systemui.statusbar.policy.NextAlarmController.NextAlarmChangeCallback Loading @@ -31,22 +34,32 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.withArgCaptor import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import java.util.Date import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.Test import org.junit.runner.RunWith import org.junit.runner.RunWith import org.mockito.ArgumentMatcher import org.mockito.ArgumentMatcher import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verify @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) class ShadeHeaderClockInteractorTest : SysuiTestCase() { class ShadeHeaderClockInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() private val kosmos = testKosmos() private val testScope = kosmos.testScope private val testScope = kosmos.testScope private val activityStarter = kosmos.activityStarter private val activityStarter = kosmos.activityStarter private val nextAlarmController = kosmos.nextAlarmController private val nextAlarmController = kosmos.nextAlarmController val underTest = kosmos.shadeHeaderClockInteractor private val underTest = kosmos.shadeHeaderClockInteractor @Test @Test fun launchClockActivity_default() = fun launchClockActivity_default() = Loading @@ -55,7 +68,7 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { verify(activityStarter) verify(activityStarter) .postStartActivityDismissingKeyguard( .postStartActivityDismissingKeyguard( argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)), argThat(IntentMatcherAction(AlarmClock.ACTION_SHOW_ALARMS)), any() any(), ) ) } } Loading @@ -71,6 +84,75 @@ class ShadeHeaderClockInteractorTest : SysuiTestCase() { underTest.launchClockActivity() underTest.launchClockActivity() verify(activityStarter).postStartActivityDismissingKeyguard(any()) verify(activityStarter).postStartActivityDismissingKeyguard(any()) } } @Test fun onTimezoneOrLocaleChanged_localeAndTimezoneChanged_emitsForEach() = testScope.runTest { val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_LOCALE_CHANGED) sendIntentActionBroadcast(Intent.ACTION_TIMEZONE_CHANGED) assertThat(timeZoneOrLocaleChanges).hasSize(4) } @Test fun onTimezoneOrLocaleChanged_timeChanged_doesNotEmit() = testScope.runTest { val timeZoneOrLocaleChanges by collectValues(underTest.onTimezoneOrLocaleChanged) assertThat(timeZoneOrLocaleChanges).hasSize(1) sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) // Expect only 1 event to have been emitted onStart, but no more. assertThat(timeZoneOrLocaleChanges).hasSize(1) } @Test fun currentTime_timeChanged() = testScope.runTest { val currentTime by collectLastValue(underTest.currentTime) sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) val earlierTime = checkNotNull(currentTime) advanceTimeBy(3.seconds) runCurrent() sendIntentActionBroadcast(Intent.ACTION_TIME_CHANGED) val laterTime = checkNotNull(currentTime) assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(3.seconds) } @Test fun currentTime_timeTicked() = testScope.runTest { val currentTime by collectLastValue(underTest.currentTime) sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) val earlierTime = checkNotNull(currentTime) advanceTimeBy(7.seconds) runCurrent() sendIntentActionBroadcast(Intent.ACTION_TIME_TICK) val laterTime = checkNotNull(currentTime) assertThat(differenceBetween(laterTime, earlierTime)).isEqualTo(7.seconds) } private fun differenceBetween(date1: Date, date2: Date): Duration { return (date1.time - date2.time).milliseconds } private fun TestScope.sendIntentActionBroadcast(intentAction: String) { kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly(context, Intent(intentAction)) runCurrent() } } } private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> { private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> { Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt +54 −85 Original line number Original line Diff line number Diff line Loading @@ -25,12 +25,15 @@ import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSingleShade import com.android.systemui.shade.domain.interactor.enableSplitShade import com.android.systemui.shade.domain.interactor.enableSplitShade import com.android.systemui.shade.domain.interactor.shadeMode import com.android.systemui.shade.shared.model.ShadeMode import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel.HeaderChipHighlight import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.fakeMobileIconsInteractor import com.android.systemui.testKosmos import com.android.systemui.testKosmos import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.argThat import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent Loading @@ -43,6 +46,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mockito.verify import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @SmallTest @RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class) @EnableSceneContainer @EnableSceneContainer Loading @@ -64,14 +68,15 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun mobileSubIds_update() = fun mobileSubIds_update() = testScope.runTest { testScope.runTest { val mobileSubIds by collectLastValue(underTest.mobileSubIds) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1) runCurrent() assertThat(mobileSubIds).isEqualTo(listOf(1)) assertThat(underTest.mobileSubIds).isEqualTo(listOf(1)) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) mobileIconsInteractor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) runCurrent() assertThat(mobileSubIds).isEqualTo(listOf(1, 2)) assertThat(underTest.mobileSubIds).isEqualTo(listOf(1, 2)) } } @Test @Test Loading Loading @@ -116,13 +121,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = fun onSystemIconChipClicked_lockedOnQsShade_collapsesShadeToLockscreen() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -134,13 +135,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = fun onSystemIconChipClicked_lockedOnNotifShade_expandsQsShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -166,13 +163,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = fun onSystemIconChipClicked_unlockedOnQsShade_collapsesShadeToGone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -184,13 +177,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = fun onSystemIconChipClicked_unlockedOnNotifShade_expandsQsShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onSystemIconChipClicked() underTest.onSystemIconChipClicked() runCurrent() runCurrent() Loading @@ -203,13 +192,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = fun onNotificationIconChipClicked_lockedOnNotifShade_collapsesShadeToLockscreen() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -221,13 +206,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = fun onNotificationIconChipClicked_lockedOnQsShade_expandsNotifShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(false) setScene(Scenes.Lockscreen) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -240,13 +221,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = fun onNotificationIconChipClicked_unlockedOnNotifShade_collapsesShadeToGone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading @@ -258,13 +235,9 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = fun onNotificationIconChipClicked_unlockedOnQsShade_expandsNotifShade() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) setDeviceEntered(true) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentOverlays).isNotEmpty() underTest.onNotificationIconChipClicked() underTest.onNotificationIconChipClicked() runCurrent() runCurrent() Loading Loading @@ -319,22 +292,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() = fun highlightChips_notifsOpenInDualShade_notifsStrongQuickSettingsWeak() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.NotificationsShade) setOverlay(Overlays.NotificationsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.NotificationsShade) setScene(Scenes.Gone) setOverlay(Overlays.NotificationsShade) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isNotEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) } } Loading @@ -342,22 +306,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() = fun highlightChips_quickSettingsOpenInDualShade_notifsWeakQuickSettingsStrong() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen, overlay = Overlays.QuickSettingsShade) setOverlay(Overlays.QuickSettingsShade) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone, overlay = Overlays.QuickSettingsShade) setScene(Scenes.Gone) setOverlay(Overlays.QuickSettingsShade) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isNotEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.Weak) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.Strong) } } Loading @@ -365,21 +320,13 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { @Test @Test fun highlightChips_noOverlaysInDualShade_bothNone() = fun highlightChips_noOverlaysInDualShade_bothNone() = testScope.runTest { testScope.runTest { kosmos.enableDualShade() val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) // Test the lockscreen scenario. // Test the lockscreen scenario. setScene(Scenes.Lockscreen) setupDualShadeState(scene = Scenes.Lockscreen) assertThat(currentOverlays).isEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) // Test the unlocked scenario. // Test the unlocked scenario. setDeviceEntered(true) setupDualShadeState(scene = Scenes.Gone) setScene(Scenes.Gone) assertThat(currentScene).isEqualTo(Scenes.Gone) assertThat(currentOverlays).isEmpty() assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.notificationsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) assertThat(underTest.quickSettingsChipHighlight).isEqualTo(HeaderChipHighlight.None) } } Loading @@ -401,21 +348,43 @@ class ShadeHeaderViewModelTest : SysuiTestCase() { ) ) } } private fun setScene(key: SceneKey) { private fun TestScope.setupDualShadeState(scene: SceneKey, overlay: OverlayKey? = null) { sceneInteractor.changeScene(key, "test") kosmos.enableDualShade() sceneInteractor.setTransitionState( val shadeMode by collectLastValue(kosmos.shadeMode) MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) val currentScene by collectLastValue(sceneInteractor.currentScene) val currentOverlays by collectLastValue(sceneInteractor.currentOverlays) if (scene == Scenes.Gone) { // Unlock the device, marking the device has been entered. kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( SuccessFingerprintAuthenticationStatus(0, true) ) ) testScope.runCurrent() } } runCurrent() assertThat(shadeMode).isEqualTo(ShadeMode.Dual) private fun setOverlay(key: OverlayKey) { sceneInteractor.changeScene(scene, "test") val currentOverlays = sceneInteractor.currentOverlays.value + key checkNotNull(currentOverlays).forEach { sceneInteractor.instantlyHideOverlay(it, "test") } sceneInteractor.showOverlay(key, "test") runCurrent() overlay?.let { sceneInteractor.showOverlay(it, "test") } sceneInteractor.setTransitionState( sceneInteractor.setTransitionState( MutableStateFlow<ObservableTransitionState>( MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(sceneInteractor.currentScene.value, currentOverlays) ObservableTransitionState.Idle(scene, setOfNotNull(overlay)) ) ) ) runCurrent() assertThat(currentScene).isEqualTo(scene) if (overlay == null) { assertThat(currentOverlays).isEmpty() } else { assertThat(currentOverlays).containsExactly(overlay) } } private fun setScene(key: SceneKey) { sceneInteractor.changeScene(key, "test") sceneInteractor.setTransitionState( MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key)) ) ) testScope.runCurrent() testScope.runCurrent() } } Loading
packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeHeaderClockInteractor.kt +36 −1 Original line number Original line Diff line number Diff line Loading @@ -17,11 +17,19 @@ package com.android.systemui.shade.domain.interactor package com.android.systemui.shade.domain.interactor import android.content.Intent import android.content.Intent import android.content.IntentFilter import android.os.UserHandle import android.provider.AlarmClock import android.provider.AlarmClock import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository import com.android.systemui.shade.data.repository.ShadeHeaderClockRepository import com.android.systemui.util.kotlin.emitOnStart import com.android.systemui.util.time.SystemClock import java.util.Date import javax.inject.Inject import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map @SysUISingleton @SysUISingleton class ShadeHeaderClockInteractor class ShadeHeaderClockInteractor Loading @@ -29,7 +37,20 @@ class ShadeHeaderClockInteractor constructor( constructor( private val repository: ShadeHeaderClockRepository, private val repository: ShadeHeaderClockRepository, private val activityStarter: ActivityStarter, private val activityStarter: ActivityStarter, private val broadcastDispatcher: BroadcastDispatcher, private val systemClock: SystemClock, ) { ) { /** [Flow] that emits `Unit` whenever the timezone or locale has changed. */ val onTimezoneOrLocaleChanged: Flow<Unit> = broadcastFlowForActions(Intent.ACTION_TIMEZONE_CHANGED, Intent.ACTION_LOCALE_CHANGED) .emitOnStart() /** [Flow] that emits the current `Date` every minute, or when the system time has changed. */ val currentTime: Flow<Date> = broadcastFlowForActions(Intent.ACTION_TIME_TICK, Intent.ACTION_TIME_CHANGED) .emitOnStart() .map { Date(systemClock.currentTimeMillis()) } /** Launch the clock activity. */ /** Launch the clock activity. */ fun launchClockActivity() { fun launchClockActivity() { val nextAlarmIntent = repository.nextAlarmIntent val nextAlarmIntent = repository.nextAlarmIntent Loading @@ -38,8 +59,22 @@ constructor( } else { } else { activityStarter.postStartActivityDismissingKeyguard( activityStarter.postStartActivityDismissingKeyguard( Intent(AlarmClock.ACTION_SHOW_ALARMS), Intent(AlarmClock.ACTION_SHOW_ALARMS), 0 0, ) ) } } } } /** * Returns a `Flow` that, when collected, emits `Unit` whenever a broadcast matching one of the * given [actionsToFilter] is received. */ private fun broadcastFlowForActions( vararg actionsToFilter: String, user: UserHandle = UserHandle.SYSTEM, ): Flow<Unit> { return broadcastDispatcher.broadcastFlow( filter = IntentFilter().apply { actionsToFilter.forEach(::addAction) }, user = user, ) } } }
packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt +41 −65 File changed.Preview size limit exceeded, changes collapsed. Show changes