Ensure that flow is run on main dispatcher

When adding and removing the callback for KeyguardUpdateMonitor, there
is an assertion that the operation must be on the main thread. The
callback was being removed on a worker thread. We can ensure that this
is not the case with flowOn.

Fixes: 274558013
Test: restart device and unlock the device several times.
Change-Id: I1f40e8b7f2cf7245f0cb17e2d0e10b9ece619234
This commit is contained in:
Aaron Liu
2023-03-28 10:22:04 -07:00
parent 8b8848020d
commit a0d7d457b3
2 changed files with 84 additions and 21 deletions

View File

@@ -25,6 +25,7 @@ import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLoggin
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.doze.DozeHost
import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
@@ -43,12 +44,14 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnl
import com.android.systemui.statusbar.phone.DozeParameters
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -195,6 +198,7 @@ constructor(
private val dozeParameters: DozeParameters,
private val authController: AuthController,
private val dreamOverlayCallbackController: DreamOverlayCallbackController,
@Main private val mainDispatcher: CoroutineDispatcher
) : KeyguardRepository {
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
override val animateBottomAreaDozingTransitions =
@@ -387,6 +391,7 @@ constructor(
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
.flowOn(mainDispatcher)
.distinctUntilChanged()
override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {

View File

@@ -46,10 +46,12 @@ import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -59,6 +61,7 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardRepositoryImplTest : SysuiTestCase() {
@@ -73,6 +76,9 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
@Mock private lateinit var dozeParameters: DozeParameters
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private lateinit var underTest: KeyguardRepositoryImpl
@@ -92,12 +98,13 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
dozeParameters,
authController,
dreamOverlayCallbackController,
mainDispatcher
)
}
@Test
fun animateBottomAreaDozingTransitions() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
underTest.setAnimateDozingTransitions(true)
@@ -112,7 +119,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun bottomAreaAlpha() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
underTest.setBottomAreaAlpha(0.1f)
@@ -133,7 +140,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun clockPosition() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
underTest.setClockPosition(0, 1)
@@ -151,11 +158,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isKeyguardShowing() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(keyguardStateController.isShowing).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
assertThat(underTest.isKeyguardShowing()).isFalse()
@@ -164,11 +172,13 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
whenever(keyguardStateController.isShowing).thenReturn(true)
captor.value.onKeyguardShowingChanged()
runCurrent()
assertThat(latest).isTrue()
assertThat(underTest.isKeyguardShowing()).isTrue()
whenever(keyguardStateController.isShowing).thenReturn(false)
captor.value.onKeyguardShowingChanged()
runCurrent()
assertThat(latest).isFalse()
assertThat(underTest.isKeyguardShowing()).isFalse()
@@ -197,11 +207,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isKeyguardOccluded() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(keyguardStateController.isOccluded).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -209,10 +220,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
whenever(keyguardStateController.isOccluded).thenReturn(true)
captor.value.onKeyguardShowingChanged()
runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isOccluded).thenReturn(false)
captor.value.onKeyguardShowingChanged()
runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -220,11 +233,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isKeyguardUnlocked() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(keyguardStateController.isUnlocked).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardUnlocked.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -232,10 +246,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
whenever(keyguardStateController.isUnlocked).thenReturn(true)
captor.value.onUnlockedChanged()
runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isUnlocked).thenReturn(false)
captor.value.onUnlockedChanged()
runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -243,82 +259,98 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isDozing() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
var latest: Boolean? = null
val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
runCurrent()
val captor = argumentCaptor<DozeHost.Callback>()
verify(dozeHost).addCallback(captor.capture())
captor.value.onDozingChanged(true)
runCurrent()
assertThat(latest).isTrue()
captor.value.onDozingChanged(false)
runCurrent()
assertThat(latest).isFalse()
job.cancel()
runCurrent()
verify(dozeHost).removeCallback(captor.value)
}
@Test
fun `isDozing - starts with correct initial value for isDozing`() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
var latest: Boolean? = null
whenever(statusBarStateController.isDozing).thenReturn(true)
var job = underTest.isDozing.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isTrue()
job.cancel()
whenever(statusBarStateController.isDozing).thenReturn(false)
job = underTest.isDozing.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
job.cancel()
}
@Test
fun dozeAmount() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<Float>()
val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
val captor = argumentCaptor<StatusBarStateController.StateListener>()
runCurrent()
verify(statusBarStateController).addCallback(captor.capture())
captor.value.onDozeAmountChanged(0.433f, 0.4f)
runCurrent()
captor.value.onDozeAmountChanged(0.498f, 0.5f)
runCurrent()
captor.value.onDozeAmountChanged(0.661f, 0.65f)
runCurrent()
assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
job.cancel()
runCurrent()
verify(statusBarStateController).removeCallback(captor.value)
}
@Test
fun wakefulness() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<WakefulnessModel>()
val job = underTest.wakefulness.onEach(values::add).launchIn(this)
runCurrent()
val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
verify(wakefulnessLifecycle).addObserver(captor.capture())
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
captor.value.onStartedWakingUp()
runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
captor.value.onFinishedWakingUp()
runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
captor.value.onStartedGoingToSleep()
runCurrent()
whenever(wakefulnessLifecycle.wakefulness)
.thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
captor.value.onFinishedGoingToSleep()
runCurrent()
assertThat(values.map { it.state })
.isEqualTo(
@@ -333,12 +365,13 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
)
job.cancel()
runCurrent()
verify(wakefulnessLifecycle).removeObserver(captor.value)
}
@Test
fun isUdfpsSupported() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
assertThat(underTest.isUdfpsSupported()).isTrue()
@@ -348,11 +381,11 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isKeyguardGoingAway() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardStateController.Callback>()
@@ -360,10 +393,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
captor.value.onKeyguardGoingAwayChanged()
runCurrent()
assertThat(latest).isTrue()
whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
captor.value.onKeyguardGoingAwayChanged()
runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -371,20 +406,23 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isDreamingFromKeyguardUpdateMonitor() =
runTest(UnconfinedTestDispatcher()) {
TestScope(mainDispatcher).runTest {
whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
captor.value.onDreamingStateChanged(true)
runCurrent()
assertThat(latest).isTrue()
captor.value.onDreamingStateChanged(false)
runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -392,11 +430,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun isDreamingFromDreamOverlayCallbackController() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
var latest: Boolean? = null
val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
runCurrent()
assertThat(latest).isFalse()
val listener =
@@ -405,9 +444,11 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
listener.onStartDream()
runCurrent()
assertThat(latest).isTrue()
listener.onWakeUp()
runCurrent()
assertThat(latest).isFalse()
job.cancel()
@@ -415,10 +456,11 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun biometricUnlockState() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<BiometricUnlockModel>()
val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
runCurrent()
val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
verify(biometricUnlockController).addBiometricModeListener(captor.capture())
@@ -435,6 +477,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
.forEach {
whenever(biometricUnlockController.mode).thenReturn(it)
captor.value.onModeChanged(it)
runCurrent()
}
assertThat(values)
@@ -454,12 +497,13 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
)
job.cancel()
runCurrent()
verify(biometricUnlockController).removeBiometricModeListener(captor.value)
}
@Test
fun dozeTransitionModel() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
// For the initial state
whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
@@ -467,6 +511,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
val values = mutableListOf<DozeTransitionModel>()
val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
runCurrent()
val listener =
withArgCaptor<DozeTransitionCallback> {
verify(dozeTransitionListener).addCallback(capture())
@@ -475,20 +520,26 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
// These don't have to reflect real transitions from the DozeMachine. Only that the
// transitions are properly emitted
listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
runCurrent()
listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
runCurrent()
listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_REQUEST_PULSE,
DozeMachine.State.DOZE_PULSING
)
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
DozeMachine.State.DOZE_PULSE_DONE
)
runCurrent()
listener.onDozeTransition(
DozeMachine.State.DOZE_AOD_PAUSING,
DozeMachine.State.DOZE_AOD_PAUSED
)
runCurrent()
assertThat(values)
.isEqualTo(
@@ -517,15 +568,17 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
)
job.cancel()
runCurrent()
verify(dozeTransitionListener).removeCallback(listener)
}
@Test
fun fingerprintSensorLocation() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
runCurrent()
val captor = argumentCaptor<AuthController.Callback>()
verify(authController).addCallback(captor.capture())
@@ -539,6 +592,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
.onEach {
whenever(authController.fingerprintSensorLocation).thenReturn(it)
captor.value.onFingerprintLocationChanged()
runCurrent()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
@@ -549,11 +603,12 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun faceSensorLocation() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<Point?>()
val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
val captor = argumentCaptor<AuthController.Callback>()
runCurrent()
verify(authController).addCallback(captor.capture())
// An initial, null value should be initially emitted so that flows combined with this
@@ -571,6 +626,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
.onEach {
whenever(authController.faceSensorLocation).thenReturn(it)
captor.value.onFaceSensorLocationChanged()
runCurrent()
}
.also { dispatchedSensorLocations ->
assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
@@ -581,10 +637,11 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Test
fun biometricUnlockSource() =
runTest(UnconfinedTestDispatcher()) {
testScope.runTest {
val values = mutableListOf<BiometricUnlockSource?>()
val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
runCurrent()
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
verify(keyguardUpdateMonitor).registerCallback(captor.capture())
@@ -603,6 +660,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
)
.onEach { biometricSourceType ->
captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
runCurrent()
}
assertThat(values)