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

Commit 57fc75ca authored by Matt Pietal's avatar Matt Pietal
Browse files

Make isInteractive use stateIn

To avoid registering multiple callbacks and to be able use retrieve
the value

Test: atest PowerRepositoryImplTest
Bug: 405159039
Flag: EXEMPT bugfix
Change-Id: I9362f5b42b254af2672f6c262c3125f3f6649eec
parent b521669e
Loading
Loading
Loading
Loading
+23 −54
Original line number Original line Diff line number Diff line
@@ -27,7 +27,10 @@ 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.broadcast.BroadcastDispatcher
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.keyguard.userActivityNotifier
import com.android.systemui.keyguard.userActivityNotifier
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.argumentCaptor
@@ -35,10 +38,8 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Before
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
@@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
class PowerRepositoryImplTest : SysuiTestCase() {
class PowerRepositoryImplTest : SysuiTestCase() {
    private val kosmos = testKosmos()
    private val kosmos = testKosmos()
    private val testScope = kosmos.testScope
    private val systemClock = FakeSystemClock()
    private val systemClock = FakeSystemClock()


    val manager: PowerManager = kosmos.powerManager
    val manager: PowerManager = kosmos.powerManager
@@ -76,94 +78,66 @@ class PowerRepositoryImplTest : SysuiTestCase() {
            PowerRepositoryImpl(
            PowerRepositoryImpl(
                manager,
                manager,
                context.applicationContext,
                context.applicationContext,
                kosmos.testScope.backgroundScope,
                systemClock,
                systemClock,
                dispatcher,
                dispatcher,
                kosmos.userActivityNotifier,
                kosmos.userActivityNotifier,
            )
            )
    }
    }


    @Test
    fun isInteractive_registersForBroadcasts() =
        runBlocking(IMMEDIATE) {
            val job = underTest.isInteractive.onEach {}.launchIn(this)

            verifyRegistered()
            assertThat(filterCaptor.value.hasAction(Intent.ACTION_SCREEN_ON)).isTrue()
            assertThat(filterCaptor.value.hasAction(Intent.ACTION_SCREEN_OFF)).isTrue()

            job.cancel()
        }

    @Test
    fun isInteractive_unregistersFromBroadcasts() =
        runBlocking(IMMEDIATE) {
            val job = underTest.isInteractive.onEach {}.launchIn(this)
            verifyRegistered()

            job.cancel()

            verify(dispatcher).unregisterReceiver(receiverCaptor.value)
        }

    @Test
    @Test
    fun isInteractive_emitsInitialTrueValueIfScreenWasOn() =
    fun isInteractive_emitsInitialTrueValueIfScreenWasOn() =
        runBlocking(IMMEDIATE) {
        testScope.runTest {
            isInteractive = true
            isInteractive = true
            var value: Boolean? = null
            val value by collectLastValue(underTest.isInteractive)
            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
            runCurrent()

            verifyRegistered()
            verifyRegistered()


            assertThat(value).isTrue()
            assertThat(value).isTrue()
            job.cancel()
        }
        }


    @Test
    @Test
    fun isInteractive_emitsInitialFalseValueIfScreenWasOff() =
    fun isInteractive_emitsInitialFalseValueIfScreenWasOff() =
        runBlocking(IMMEDIATE) {
        testScope.runTest {
            isInteractive = false
            isInteractive = false
            var value: Boolean? = null
            val value by collectLastValue(underTest.isInteractive)
            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
            runCurrent()

            verifyRegistered()
            verifyRegistered()


            assertThat(value).isFalse()
            assertThat(value).isFalse()
            job.cancel()
        }
        }


    @Test
    @Test
    fun isInteractive_emitsTrueWhenTheScreenTurnsOn() =
    fun isInteractive_emitsTrueWhenTheScreenTurnsOn() =
        runBlocking(IMMEDIATE) {
        testScope.runTest {
            var value: Boolean? = null
            val value by collectLastValue(underTest.isInteractive)
            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
            runCurrent()
            verifyRegistered()
            verifyRegistered()


            isInteractive = true
            isInteractive = true
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_ON))
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_ON))


            assertThat(value).isTrue()
            assertThat(value).isTrue()
            job.cancel()
        }
        }


    @Test
    @Test
    fun isInteractive_emitsFalseWhenTheScreenTurnsOff() =
    fun isInteractive_emitsFalseWhenTheScreenTurnsOff() =
        runBlocking(IMMEDIATE) {
        testScope.runTest {
            var value: Boolean? = null
            val value by collectLastValue(underTest.isInteractive)
            val job = underTest.isInteractive.onEach { value = it }.launchIn(this)
            runCurrent()
            verifyRegistered()
            verifyRegistered()


            isInteractive = false
            isInteractive = false
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))


            assertThat(value).isFalse()
            assertThat(value).isFalse()
            job.cancel()
        }
        }


    @Test
    @Test
    fun isInteractive_emitsCorrectlyOverTime() =
    fun isInteractive_emitsCorrectlyOverTime() =
        runBlocking(IMMEDIATE) {
        testScope.runTest {
            val values = mutableListOf<Boolean>()
            val values by collectValues(underTest.isInteractive)
            val job = underTest.isInteractive.onEach(values::add).launchIn(this)
            runCurrent()
            verifyRegistered()
            verifyRegistered()


            isInteractive = false
            isInteractive = false
@@ -173,8 +147,7 @@ class PowerRepositoryImplTest : SysuiTestCase() {
            isInteractive = false
            isInteractive = false
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))
            receiverCaptor.value.onReceive(context, Intent(Intent.ACTION_SCREEN_OFF))


            assertThat(values).isEqualTo(listOf(true, false, true, false))
            assertThat(values).isEqualTo(listOf(false, true, false))
            job.cancel()
        }
        }


    @Test
    @Test
@@ -247,8 +220,4 @@ class PowerRepositoryImplTest : SysuiTestCase() {
                isNull(),
                isNull(),
            )
            )
    }
    }

    companion object {
        private val IMMEDIATE = Dispatchers.Main.immediate
    }
}
}
+28 −23
Original line number Original line Diff line number Diff line
@@ -37,16 +37,18 @@ import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.util.time.SystemClock
import com.android.systemui.util.time.SystemClock
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.stateIn


/** Defines interface for classes that act as source of truth for power-related data. */
/** Defines interface for classes that act as source of truth for power-related data. */
interface PowerRepository {
interface PowerRepository {
    /** Whether the device is interactive. Starts with the current state. */
    /** Whether the device is interactive. Starts with the current state. */
    val isInteractive: Flow<Boolean>
    val isInteractive: StateFlow<Boolean>


    /**
    /**
     * Whether the device is awake or asleep. [WakefulnessState.AWAKE] means the screen is fully
     * Whether the device is awake or asleep. [WakefulnessState.AWAKE] means the screen is fully
@@ -103,6 +105,7 @@ class PowerRepositoryImpl
constructor(
constructor(
    private val manager: PowerManager,
    private val manager: PowerManager,
    @Application private val applicationContext: Context,
    @Application private val applicationContext: Context,
    @Application private val scope: CoroutineScope,
    private val systemClock: SystemClock,
    private val systemClock: SystemClock,
    dispatcher: BroadcastDispatcher,
    dispatcher: BroadcastDispatcher,
    private val userActivityNotifier: UserActivityNotifier,
    private val userActivityNotifier: UserActivityNotifier,
@@ -110,7 +113,8 @@ constructor(


    override val dozeScreenState = MutableStateFlow(DozeScreenStateModel.UNKNOWN)
    override val dozeScreenState = MutableStateFlow(DozeScreenStateModel.UNKNOWN)


    override val isInteractive: Flow<Boolean> = conflatedCallbackFlow {
    override val isInteractive: StateFlow<Boolean> =
        conflatedCallbackFlow {
                fun send() {
                fun send() {
                    trySendWithFailureLogging(manager.isInteractive, TAG)
                    trySendWithFailureLogging(manager.isInteractive, TAG)
                }
                }
@@ -133,6 +137,7 @@ constructor(


                awaitClose { dispatcher.unregisterReceiver(receiver) }
                awaitClose { dispatcher.unregisterReceiver(receiver) }
            }
            }
            .stateIn(scope, SharingStarted.Eagerly, false)


    private val _wakefulness = MutableStateFlow(WakefulnessModel())
    private val _wakefulness = MutableStateFlow(WakefulnessModel())
    override val wakefulness = _wakefulness.asStateFlow()
    override val wakefulness = _wakefulness.asStateFlow()
+1 −2
Original line number Original line Diff line number Diff line
@@ -34,7 +34,6 @@ import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import com.android.systemui.statusbar.phone.ScreenOffAnimationController
import javax.inject.Inject
import javax.inject.Inject
import javax.inject.Provider
import javax.inject.Provider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collect
@@ -53,7 +52,7 @@ constructor(
    private val cameraGestureHelper: Provider<CameraGestureHelper?>,
    private val cameraGestureHelper: Provider<CameraGestureHelper?>,
) {
) {
    /** Whether the screen is on or off. */
    /** Whether the screen is on or off. */
    val isInteractive: Flow<Boolean> = repository.isInteractive
    val isInteractive: StateFlow<Boolean> = repository.isInteractive


    /**
    /**
     * Whether we're awake or asleep, along with additional information about why we're awake/asleep
     * Whether we're awake or asleep, along with additional information about why we're awake/asleep
+2 −2
Original line number Original line Diff line number Diff line
@@ -27,14 +27,14 @@ import com.android.systemui.power.shared.model.WakefulnessState
import dagger.Binds
import dagger.Binds
import dagger.Module
import dagger.Module
import javax.inject.Inject
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.asStateFlow


@SysUISingleton
@SysUISingleton
class FakePowerRepository @Inject constructor() : PowerRepository {
class FakePowerRepository @Inject constructor() : PowerRepository {
    private val _isInteractive = MutableStateFlow(true)
    private val _isInteractive = MutableStateFlow(true)
    override val isInteractive: Flow<Boolean> = _isInteractive.asStateFlow()
    override val isInteractive: StateFlow<Boolean> = _isInteractive.asStateFlow()


    private val _wakefulness = MutableStateFlow(WakefulnessModel())
    private val _wakefulness = MutableStateFlow(WakefulnessModel())
    override val wakefulness = _wakefulness.asStateFlow()
    override val wakefulness = _wakefulness.asStateFlow()