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

Commit 1088bc43 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[Dual Shade] Refactor ShadeHeader's view-model and clock interactor." into main

parents c9bbae6f 939097d9
Loading
Loading
Loading
Loading
+8 −19
Original line number Original line Diff line number Diff line
@@ -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()
@@ -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),
                )
                )
@@ -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)) {
@@ -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),
                )
                )
@@ -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,
                        )
                        )
                    }
                    }
@@ -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(
+85 −3
Original line number Original line Diff line number Diff line
@@ -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
@@ -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() =
@@ -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(),
                )
                )
        }
        }


@@ -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> {
+54 −85
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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()
@@ -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)
        }
        }
@@ -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)
        }
        }
@@ -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)
        }
        }
@@ -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()
    }
    }
+36 −1
Original line number Original line Diff line number Diff line
@@ -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
@@ -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
@@ -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,
        )
    }
}
}
+41 −65

File changed.

Preview size limit exceeded, changes collapsed.

Loading