1  /*
2   * Copyright (C) 2022 The Android Open Source Project
3   *
4   * Licensed under the Apache License, Version 2.0 (the "License");
5   * you may not use this file except in compliance with the License.
6   * You may obtain a copy of the License at
7   *
8   *      http://www.apache.org/licenses/LICENSE-2.0
9   *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.android.systemui.keyguard.data.repository
18  
19  import android.graphics.Point
20  import android.hardware.biometrics.BiometricSourceType
21  import androidx.test.ext.junit.runners.AndroidJUnit4
22  import androidx.test.filters.SmallTest
23  import com.android.keyguard.KeyguardUpdateMonitor
24  import com.android.keyguard.KeyguardUpdateMonitorCallback
25  import com.android.systemui.RoboPilotTest
26  import com.android.systemui.SysuiTestCase
27  import com.android.systemui.biometrics.AuthController
28  import com.android.systemui.common.shared.model.Position
29  import com.android.systemui.coroutines.collectLastValue
30  import com.android.systemui.doze.DozeMachine
31  import com.android.systemui.doze.DozeTransitionCallback
32  import com.android.systemui.doze.DozeTransitionListener
33  import com.android.systemui.dreams.DreamOverlayCallbackController
34  import com.android.systemui.keyguard.ScreenLifecycle
35  import com.android.systemui.keyguard.WakefulnessLifecycle
36  import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
37  import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
38  import com.android.systemui.keyguard.shared.model.DozeStateModel
39  import com.android.systemui.keyguard.shared.model.DozeTransitionModel
40  import com.android.systemui.keyguard.shared.model.ScreenModel
41  import com.android.systemui.keyguard.shared.model.ScreenState
42  import com.android.systemui.keyguard.shared.model.WakefulnessModel
43  import com.android.systemui.keyguard.shared.model.WakefulnessState
44  import com.android.systemui.plugins.statusbar.StatusBarStateController
45  import com.android.systemui.statusbar.phone.BiometricUnlockController
46  import com.android.systemui.statusbar.phone.DozeParameters
47  import com.android.systemui.statusbar.phone.KeyguardBypassController
48  import com.android.systemui.statusbar.policy.KeyguardStateController
49  import com.android.systemui.util.mockito.argumentCaptor
50  import com.android.systemui.util.mockito.whenever
51  import com.android.systemui.util.mockito.withArgCaptor
52  import com.android.systemui.util.time.FakeSystemClock
53  import com.google.common.truth.Truth.assertThat
54  import kotlinx.coroutines.ExperimentalCoroutinesApi
55  import kotlinx.coroutines.flow.launchIn
56  import kotlinx.coroutines.flow.onCompletion
57  import kotlinx.coroutines.flow.onEach
58  import kotlinx.coroutines.test.StandardTestDispatcher
59  import kotlinx.coroutines.test.TestScope
60  import kotlinx.coroutines.test.runCurrent
61  import kotlinx.coroutines.test.runTest
62  import org.junit.Before
63  import org.junit.Test
64  import org.junit.runner.RunWith
65  import org.mockito.Mock
66  import org.mockito.Mockito.atLeastOnce
67  import org.mockito.Mockito.verify
68  import org.mockito.MockitoAnnotations
69  
70  @OptIn(ExperimentalCoroutinesApi::class)
71  @SmallTest
72  @RoboPilotTest
73  @RunWith(AndroidJUnit4::class)
74  class KeyguardRepositoryImplTest : SysuiTestCase() {
75  
76      @Mock private lateinit var statusBarStateController: StatusBarStateController
77      @Mock private lateinit var keyguardStateController: KeyguardStateController
78      @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
79      @Mock private lateinit var screenLifecycle: ScreenLifecycle
80      @Mock private lateinit var biometricUnlockController: BiometricUnlockController
81      @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
82      @Mock private lateinit var authController: AuthController
83      @Mock private lateinit var keyguardBypassController: KeyguardBypassController
84      @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
85      @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
86      @Mock private lateinit var dozeParameters: DozeParameters
87      private val mainDispatcher = StandardTestDispatcher()
88      private val testDispatcher = StandardTestDispatcher()
89      private val testScope = TestScope(testDispatcher)
90      private lateinit var systemClock: FakeSystemClock
91  
92      private lateinit var underTest: KeyguardRepositoryImpl
93  
94      @Before
95      fun setUp() {
96          MockitoAnnotations.initMocks(this)
97          systemClock = FakeSystemClock()
98          underTest =
99              KeyguardRepositoryImpl(
100                  statusBarStateController,
101                  wakefulnessLifecycle,
102                  screenLifecycle,
103                  biometricUnlockController,
104                  keyguardStateController,
105                  keyguardBypassController,
106                  keyguardUpdateMonitor,
107                  dozeTransitionListener,
108                  dozeParameters,
109                  authController,
110                  dreamOverlayCallbackController,
111                  mainDispatcher,
112                  testScope.backgroundScope,
113                  systemClock,
114              )
115      }
116  
117      @Test
118      fun animateBottomAreaDozingTransitions() =
119          testScope.runTest {
120              assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
121  
122              underTest.setAnimateDozingTransitions(true)
123              assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
124  
125              underTest.setAnimateDozingTransitions(false)
126              assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
127  
128              underTest.setAnimateDozingTransitions(true)
129              assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
130          }
131  
132      @Test
133      fun bottomAreaAlpha() =
134          testScope.runTest {
135              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
136  
137              underTest.setBottomAreaAlpha(0.1f)
138              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
139  
140              underTest.setBottomAreaAlpha(0.2f)
141              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
142  
143              underTest.setBottomAreaAlpha(0.3f)
144              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
145  
146              underTest.setBottomAreaAlpha(0.5f)
147              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
148  
149              underTest.setBottomAreaAlpha(1.0f)
150              assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
151          }
152  
153      @Test
154      fun clockPosition() =
155          testScope.runTest {
156              assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
157  
158              underTest.setClockPosition(0, 1)
159              assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
160  
161              underTest.setClockPosition(1, 9)
162              assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
163  
164              underTest.setClockPosition(1, 0)
165              assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
166  
167              underTest.setClockPosition(3, 1)
168              assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
169          }
170  
171      @Test
172      fun dozeTimeTick() =
173          testScope.runTest {
174              val lastDozeTimeTick by collectLastValue(underTest.dozeTimeTick)
175              assertThat(lastDozeTimeTick).isEqualTo(0L)
176  
177              // WHEN dozeTimeTick updated
178              systemClock.setUptimeMillis(systemClock.uptimeMillis() + 5)
179              underTest.dozeTimeTick()
180  
181              // THEN listeners were updated to the latest uptime millis
182              assertThat(systemClock.uptimeMillis()).isEqualTo(lastDozeTimeTick)
183          }
184  
185      @Test
186      fun isKeyguardShowing() =
187          testScope.runTest {
188              whenever(keyguardStateController.isShowing).thenReturn(false)
189              var latest: Boolean? = null
190              val job = underTest.isKeyguardShowing.onEach { latest = it }.launchIn(this)
191  
192              runCurrent()
193              assertThat(latest).isFalse()
194              assertThat(underTest.isKeyguardShowing()).isFalse()
195  
196              val captor = argumentCaptor<KeyguardStateController.Callback>()
197              verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
198  
199              whenever(keyguardStateController.isShowing).thenReturn(true)
200              captor.value.onKeyguardShowingChanged()
201              runCurrent()
202              assertThat(latest).isTrue()
203              assertThat(underTest.isKeyguardShowing()).isTrue()
204  
205              whenever(keyguardStateController.isShowing).thenReturn(false)
206              captor.value.onKeyguardShowingChanged()
207              runCurrent()
208              assertThat(latest).isFalse()
209              assertThat(underTest.isKeyguardShowing()).isFalse()
210  
211              job.cancel()
212          }
213  
214      @Test
215      fun isBypassEnabled_disabledInController() {
216          whenever(keyguardBypassController.isBypassEnabled).thenReturn(false)
217          whenever(keyguardBypassController.bypassEnabled).thenReturn(false)
218          assertThat(underTest.isBypassEnabled()).isFalse()
219      }
220  
221      @Test
222      fun isBypassEnabled_enabledInController() {
223          whenever(keyguardBypassController.isBypassEnabled).thenReturn(true)
224          whenever(keyguardBypassController.bypassEnabled).thenReturn(true)
225          assertThat(underTest.isBypassEnabled()).isTrue()
226      }
227  
228      @Test
229      fun isAodAvailable() = runTest {
230          val flow = underTest.isAodAvailable
231          var isAodAvailable = collectLastValue(flow)
232          runCurrent()
233  
234          val callback =
235              withArgCaptor<DozeParameters.Callback> { verify(dozeParameters).addCallback(capture()) }
236  
237          whenever(dozeParameters.getAlwaysOn()).thenReturn(false)
238          callback.onAlwaysOnChange()
239          assertThat(isAodAvailable()).isEqualTo(false)
240  
241          whenever(dozeParameters.getAlwaysOn()).thenReturn(true)
242          callback.onAlwaysOnChange()
243          assertThat(isAodAvailable()).isEqualTo(true)
244  
245          flow.onCompletion { verify(dozeParameters).removeCallback(callback) }
246      }
247  
248      @Test
249      fun isKeyguardOccluded() =
250          testScope.runTest {
251              whenever(keyguardStateController.isOccluded).thenReturn(false)
252              var latest: Boolean? = null
253              val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
254  
255              runCurrent()
256              assertThat(latest).isFalse()
257  
258              val captor = argumentCaptor<KeyguardStateController.Callback>()
259              verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
260  
261              whenever(keyguardStateController.isOccluded).thenReturn(true)
262              captor.value.onKeyguardShowingChanged()
263              runCurrent()
264              assertThat(latest).isTrue()
265  
266              whenever(keyguardStateController.isOccluded).thenReturn(false)
267              captor.value.onKeyguardShowingChanged()
268              runCurrent()
269              assertThat(latest).isFalse()
270  
271              job.cancel()
272          }
273  
274      @Test
275      fun isKeyguardUnlocked() =
276          testScope.runTest {
277              whenever(keyguardStateController.isUnlocked).thenReturn(false)
278              val isKeyguardUnlocked by collectLastValue(underTest.isKeyguardUnlocked)
279  
280              runCurrent()
281              assertThat(isKeyguardUnlocked).isFalse()
282  
283              val captor = argumentCaptor<KeyguardStateController.Callback>()
284              verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
285  
286              whenever(keyguardStateController.isUnlocked).thenReturn(true)
287              captor.value.onUnlockedChanged()
288              runCurrent()
289              assertThat(isKeyguardUnlocked).isTrue()
290  
291              whenever(keyguardStateController.isUnlocked).thenReturn(false)
292              captor.value.onKeyguardShowingChanged()
293              runCurrent()
294              assertThat(isKeyguardUnlocked).isFalse()
295          }
296  
297      @Test
298      fun isDozing() =
299          testScope.runTest {
300              underTest.setIsDozing(true)
301              assertThat(underTest.isDozing.value).isEqualTo(true)
302  
303              underTest.setIsDozing(false)
304              assertThat(underTest.isDozing.value).isEqualTo(false)
305          }
306  
307      @Test
308      fun isDozing_startsWithCorrectInitialValueForIsDozing() =
309          testScope.runTest {
310              assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(null)
311  
312              val expectedPoint = Point(100, 200)
313              underTest.setLastDozeTapToWakePosition(expectedPoint)
314              assertThat(underTest.lastDozeTapToWakePosition.value).isEqualTo(expectedPoint)
315          }
316  
317      @Test
318      fun dozeAmount() =
319          testScope.runTest {
320              val values = mutableListOf<Float>()
321              val job = underTest.linearDozeAmount.onEach(values::add).launchIn(this)
322  
323              val captor = argumentCaptor<StatusBarStateController.StateListener>()
324              runCurrent()
325              verify(statusBarStateController).addCallback(captor.capture())
326  
327              captor.value.onDozeAmountChanged(0.433f, 0.4f)
328              runCurrent()
329              captor.value.onDozeAmountChanged(0.498f, 0.5f)
330              runCurrent()
331              captor.value.onDozeAmountChanged(0.661f, 0.65f)
332              runCurrent()
333  
334              assertThat(values).isEqualTo(listOf(0f, 0.433f, 0.498f, 0.661f))
335  
336              job.cancel()
337              runCurrent()
338              verify(statusBarStateController).removeCallback(captor.value)
339          }
340  
341      @Test
342      fun isActiveDreamLockscreenHosted() =
343          testScope.runTest {
344              underTest.setIsActiveDreamLockscreenHosted(true)
345              assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(true)
346  
347              underTest.setIsActiveDreamLockscreenHosted(false)
348              assertThat(underTest.isActiveDreamLockscreenHosted.value).isEqualTo(false)
349          }
350  
351      @Test
352      fun wakefulness() =
353          testScope.runTest {
354              val values = mutableListOf<WakefulnessModel>()
355              val job = underTest.wakefulness.onEach(values::add).launchIn(this)
356  
357              runCurrent()
358              val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
359              verify(wakefulnessLifecycle).addObserver(captor.capture())
360  
361              whenever(wakefulnessLifecycle.wakefulness)
362                  .thenReturn(WakefulnessLifecycle.WAKEFULNESS_WAKING)
363              captor.value.onStartedWakingUp()
364              runCurrent()
365  
366              whenever(wakefulnessLifecycle.wakefulness)
367                  .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE)
368              captor.value.onFinishedWakingUp()
369              runCurrent()
370  
371              whenever(wakefulnessLifecycle.wakefulness)
372                  .thenReturn(WakefulnessLifecycle.WAKEFULNESS_GOING_TO_SLEEP)
373              captor.value.onStartedGoingToSleep()
374              runCurrent()
375  
376              whenever(wakefulnessLifecycle.wakefulness)
377                  .thenReturn(WakefulnessLifecycle.WAKEFULNESS_ASLEEP)
378              captor.value.onFinishedGoingToSleep()
379              runCurrent()
380  
381              assertThat(values.map { it.state })
382                  .isEqualTo(
383                      listOf(
384                          // Initial value will be ASLEEP
385                          WakefulnessState.ASLEEP,
386                          WakefulnessState.STARTING_TO_WAKE,
387                          WakefulnessState.AWAKE,
388                          WakefulnessState.STARTING_TO_SLEEP,
389                          WakefulnessState.ASLEEP,
390                      )
391                  )
392  
393              job.cancel()
394          }
395  
396      @Test
397      fun screenModel() =
398          testScope.runTest {
399              val values = mutableListOf<ScreenModel>()
400              val job = underTest.screenModel.onEach(values::add).launchIn(this)
401  
402              runCurrent()
403              val captor = argumentCaptor<ScreenLifecycle.Observer>()
404              verify(screenLifecycle).addObserver(captor.capture())
405  
406              whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_TURNING_ON)
407              captor.value.onScreenTurningOn()
408              runCurrent()
409  
410              whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON)
411              captor.value.onScreenTurnedOn()
412              runCurrent()
413  
414              whenever(screenLifecycle.getScreenState())
415                  .thenReturn(ScreenLifecycle.SCREEN_TURNING_OFF)
416              captor.value.onScreenTurningOff()
417              runCurrent()
418  
419              whenever(screenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_OFF)
420              captor.value.onScreenTurnedOff()
421              runCurrent()
422  
423              assertThat(values.map { it.state })
424                  .isEqualTo(
425                      listOf(
426                          // Initial value will be OFF
427                          ScreenState.SCREEN_OFF,
428                          ScreenState.SCREEN_TURNING_ON,
429                          ScreenState.SCREEN_ON,
430                          ScreenState.SCREEN_TURNING_OFF,
431                          ScreenState.SCREEN_OFF,
432                      )
433                  )
434  
435              job.cancel()
436          }
437  
438      @Test
439      fun isUdfpsSupported() =
440          testScope.runTest {
441              whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(true)
442              assertThat(underTest.isUdfpsSupported()).isTrue()
443  
444              whenever(keyguardUpdateMonitor.isUdfpsSupported).thenReturn(false)
445              assertThat(underTest.isUdfpsSupported()).isFalse()
446          }
447  
448      @Test
449      fun isKeyguardGoingAway() =
450          testScope.runTest {
451              whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
452              var latest: Boolean? = null
453              val job = underTest.isKeyguardGoingAway.onEach { latest = it }.launchIn(this)
454              runCurrent()
455              assertThat(latest).isFalse()
456  
457              val captor = argumentCaptor<KeyguardStateController.Callback>()
458              verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
459  
460              whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
461              captor.value.onKeyguardGoingAwayChanged()
462              runCurrent()
463              assertThat(latest).isTrue()
464  
465              whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(false)
466              captor.value.onKeyguardGoingAwayChanged()
467              runCurrent()
468              assertThat(latest).isFalse()
469  
470              job.cancel()
471          }
472  
473      @Test
474      fun isDreamingFromKeyguardUpdateMonitor() =
475          TestScope(mainDispatcher).runTest {
476              whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
477              var latest: Boolean? = null
478              val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
479  
480              runCurrent()
481              assertThat(latest).isFalse()
482  
483              val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
484              verify(keyguardUpdateMonitor).registerCallback(captor.capture())
485  
486              captor.value.onDreamingStateChanged(true)
487              runCurrent()
488              assertThat(latest).isTrue()
489  
490              captor.value.onDreamingStateChanged(false)
491              runCurrent()
492              assertThat(latest).isFalse()
493  
494              job.cancel()
495          }
496  
497      @Test
498      fun isDreamingFromDreamOverlayCallbackController() =
499          testScope.runTest {
500              whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
501              var latest: Boolean? = null
502              val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
503  
504              runCurrent()
505              assertThat(latest).isFalse()
506  
507              val listener =
508                  withArgCaptor<DreamOverlayCallbackController.Callback> {
509                      verify(dreamOverlayCallbackController).addCallback(capture())
510                  }
511  
512              listener.onStartDream()
513              runCurrent()
514              assertThat(latest).isTrue()
515  
516              listener.onWakeUp()
517              runCurrent()
518              assertThat(latest).isFalse()
519  
520              job.cancel()
521          }
522  
523      @Test
524      fun biometricUnlockState() =
525          testScope.runTest {
526              val values = mutableListOf<BiometricUnlockModel>()
527              val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
528  
529              runCurrent()
530              val captor = argumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener>()
531              verify(biometricUnlockController).addListener(captor.capture())
532  
533              listOf(
534                      BiometricUnlockController.MODE_NONE,
535                      BiometricUnlockController.MODE_WAKE_AND_UNLOCK,
536                      BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING,
537                      BiometricUnlockController.MODE_SHOW_BOUNCER,
538                      BiometricUnlockController.MODE_ONLY_WAKE,
539                      BiometricUnlockController.MODE_UNLOCK_COLLAPSING,
540                      BiometricUnlockController.MODE_DISMISS_BOUNCER,
541                      BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM,
542                  )
543                  .forEach {
544                      whenever(biometricUnlockController.mode).thenReturn(it)
545                      captor.value.onModeChanged(it)
546                      runCurrent()
547                  }
548  
549              assertThat(values)
550                  .isEqualTo(
551                      listOf(
552                          // Initial value will be NONE, followed by onModeChanged() call
553                          BiometricUnlockModel.NONE,
554                          BiometricUnlockModel.NONE,
555                          BiometricUnlockModel.WAKE_AND_UNLOCK,
556                          BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
557                          BiometricUnlockModel.SHOW_BOUNCER,
558                          BiometricUnlockModel.ONLY_WAKE,
559                          BiometricUnlockModel.UNLOCK_COLLAPSING,
560                          BiometricUnlockModel.DISMISS_BOUNCER,
561                          BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
562                      )
563                  )
564  
565              job.cancel()
566              runCurrent()
567              verify(biometricUnlockController).removeListener(captor.value)
568          }
569  
570      @Test
571      fun dozeTransitionModel() =
572          testScope.runTest {
573              // For the initial state
574              whenever(dozeTransitionListener.oldState).thenReturn(DozeMachine.State.UNINITIALIZED)
575              whenever(dozeTransitionListener.newState).thenReturn(DozeMachine.State.UNINITIALIZED)
576  
577              val values = mutableListOf<DozeTransitionModel>()
578              val job = underTest.dozeTransitionModel.onEach(values::add).launchIn(this)
579  
580              runCurrent()
581              val listener =
582                  withArgCaptor<DozeTransitionCallback> {
583                      verify(dozeTransitionListener).addCallback(capture())
584                  }
585  
586              // These don't have to reflect real transitions from the DozeMachine. Only that the
587              // transitions are properly emitted
588              listener.onDozeTransition(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE)
589              runCurrent()
590              listener.onDozeTransition(DozeMachine.State.DOZE, DozeMachine.State.DOZE_AOD)
591              runCurrent()
592              listener.onDozeTransition(DozeMachine.State.DOZE_AOD_DOCKED, DozeMachine.State.FINISH)
593              runCurrent()
594              listener.onDozeTransition(
595                  DozeMachine.State.DOZE_REQUEST_PULSE,
596                  DozeMachine.State.DOZE_PULSING
597              )
598              runCurrent()
599              listener.onDozeTransition(
600                  DozeMachine.State.DOZE_SUSPEND_TRIGGERS,
601                  DozeMachine.State.DOZE_PULSE_DONE
602              )
603              runCurrent()
604              listener.onDozeTransition(
605                  DozeMachine.State.DOZE_AOD_PAUSING,
606                  DozeMachine.State.DOZE_AOD_PAUSED
607              )
608              runCurrent()
609  
610              assertThat(values)
611                  .isEqualTo(
612                      listOf(
613                          // Initial value will be UNINITIALIZED
614                          DozeTransitionModel(
615                              DozeStateModel.UNINITIALIZED,
616                              DozeStateModel.UNINITIALIZED
617                          ),
618                          DozeTransitionModel(DozeStateModel.INITIALIZED, DozeStateModel.DOZE),
619                          DozeTransitionModel(DozeStateModel.DOZE, DozeStateModel.DOZE_AOD),
620                          DozeTransitionModel(DozeStateModel.DOZE_AOD_DOCKED, DozeStateModel.FINISH),
621                          DozeTransitionModel(
622                              DozeStateModel.DOZE_REQUEST_PULSE,
623                              DozeStateModel.DOZE_PULSING
624                          ),
625                          DozeTransitionModel(
626                              DozeStateModel.DOZE_SUSPEND_TRIGGERS,
627                              DozeStateModel.DOZE_PULSE_DONE
628                          ),
629                          DozeTransitionModel(
630                              DozeStateModel.DOZE_AOD_PAUSING,
631                              DozeStateModel.DOZE_AOD_PAUSED
632                          ),
633                      )
634                  )
635  
636              job.cancel()
637              runCurrent()
638              verify(dozeTransitionListener).removeCallback(listener)
639          }
640  
641      @Test
642      fun fingerprintSensorLocation() =
643          testScope.runTest {
644              val values = mutableListOf<Point?>()
645              val job = underTest.fingerprintSensorLocation.onEach(values::add).launchIn(this)
646  
647              runCurrent()
648              val captor = argumentCaptor<AuthController.Callback>()
649              verify(authController).addCallback(captor.capture())
650  
651              // An initial, null value should be initially emitted so that flows combined with this
652              // one
653              // emit values immediately. The sensor location is expected to be nullable, so anyone
654              // consuming it should handle that properly.
655              assertThat(values).isEqualTo(listOf(null))
656  
657              listOf(Point(500, 500), Point(0, 0), null, Point(250, 250))
658                  .onEach {
659                      whenever(authController.fingerprintSensorLocation).thenReturn(it)
660                      captor.value.onFingerprintLocationChanged()
661                      runCurrent()
662                  }
663                  .also { dispatchedSensorLocations ->
664                      assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
665                  }
666  
667              job.cancel()
668          }
669  
670      @Test
671      fun faceSensorLocation() =
672          testScope.runTest {
673              val values = mutableListOf<Point?>()
674              val job = underTest.faceSensorLocation.onEach(values::add).launchIn(this)
675  
676              val captor = argumentCaptor<AuthController.Callback>()
677              runCurrent()
678              verify(authController).addCallback(captor.capture())
679  
680              // An initial, null value should be initially emitted so that flows combined with this
681              // one
682              // emit values immediately. The sensor location is expected to be nullable, so anyone
683              // consuming it should handle that properly.
684              assertThat(values).isEqualTo(listOf(null))
685  
686              listOf(
687                      Point(500, 500),
688                      Point(0, 0),
689                      null,
690                      Point(250, 250),
691                  )
692                  .onEach {
693                      whenever(authController.faceSensorLocation).thenReturn(it)
694                      captor.value.onFaceSensorLocationChanged()
695                      runCurrent()
696                  }
697                  .also { dispatchedSensorLocations ->
698                      assertThat(values).isEqualTo(listOf(null) + dispatchedSensorLocations)
699                  }
700  
701              job.cancel()
702          }
703  
704      @Test
705      fun biometricUnlockSource() =
706          testScope.runTest {
707              val values = mutableListOf<BiometricUnlockSource?>()
708              val job = underTest.biometricUnlockSource.onEach(values::add).launchIn(this)
709  
710              runCurrent()
711              val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
712              verify(keyguardUpdateMonitor).registerCallback(captor.capture())
713  
714              // An initial, null value should be initially emitted so that flows combined with this
715              // one
716              // emit values immediately. The biometric unlock source is expected to be nullable, so
717              // anyone consuming it should handle that properly.
718              assertThat(values).isEqualTo(listOf(null))
719  
720              listOf(
721                      BiometricSourceType.FINGERPRINT,
722                      BiometricSourceType.IRIS,
723                      null,
724                      BiometricSourceType.FACE,
725                      BiometricSourceType.FINGERPRINT,
726                  )
727                  .onEach { biometricSourceType ->
728                      captor.value.onBiometricAuthenticated(0, biometricSourceType, false)
729                      runCurrent()
730                  }
731  
732              assertThat(values)
733                  .isEqualTo(
734                      listOf(
735                          null,
736                          BiometricUnlockSource.FINGERPRINT_SENSOR,
737                          BiometricUnlockSource.FACE_SENSOR,
738                          null,
739                          BiometricUnlockSource.FACE_SENSOR,
740                          BiometricUnlockSource.FINGERPRINT_SENSOR,
741                      )
742                  )
743  
744              job.cancel()
745          }
746  }
747