1 /*
2  * Copyright (C) 2023 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.authentication.domain.interactor
18 
19 import android.app.admin.DevicePolicyManager
20 import androidx.test.filters.SmallTest
21 import com.android.systemui.SysuiTestCase
22 import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
23 import com.android.systemui.authentication.data.repository.AuthenticationRepository
24 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
25 import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
26 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
27 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
28 import com.android.systemui.coroutines.collectLastValue
29 import com.android.systemui.scene.SceneTestUtils
30 import com.android.systemui.scene.shared.model.SceneKey
31 import com.android.systemui.scene.shared.model.SceneModel
32 import com.google.common.truth.Truth.assertThat
33 import kotlin.time.Duration.Companion.milliseconds
34 import kotlin.time.Duration.Companion.seconds
35 import kotlinx.coroutines.ExperimentalCoroutinesApi
36 import kotlinx.coroutines.test.TestScope
37 import kotlinx.coroutines.test.advanceTimeBy
38 import kotlinx.coroutines.test.runCurrent
39 import kotlinx.coroutines.test.runTest
40 import org.junit.Test
41 import org.junit.runner.RunWith
42 import org.junit.runners.JUnit4
43 
44 @OptIn(ExperimentalCoroutinesApi::class)
45 @SmallTest
46 @RunWith(JUnit4::class)
47 class AuthenticationInteractorTest : SysuiTestCase() {
48 
49     private val utils = SceneTestUtils(this)
50     private val testScope = utils.testScope
51     private val repository: AuthenticationRepository = utils.authenticationRepository()
52     private val sceneInteractor = utils.sceneInteractor()
53     private val underTest =
54         utils.authenticationInteractor(
55             repository = repository,
56             sceneInteractor = sceneInteractor,
57         )
58 
59     @Test
60     fun authenticationMethod() =
61         testScope.runTest {
62             val authMethod by collectLastValue(underTest.authenticationMethod)
63             runCurrent()
64             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
65             assertThat(underTest.getAuthenticationMethod())
66                 .isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
67 
68             utils.authenticationRepository.setAuthenticationMethod(
69                 DataLayerAuthenticationMethodModel.Password
70             )
71 
72             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password)
73             assertThat(underTest.getAuthenticationMethod())
74                 .isEqualTo(DomainLayerAuthenticationMethodModel.Password)
75         }
76 
77     @Test
78     fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
79         testScope.runTest {
80             val authMethod by collectLastValue(underTest.authenticationMethod)
81             runCurrent()
82 
83             utils.authenticationRepository.apply {
84                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
85                 setLockscreenEnabled(true)
86             }
87 
88             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
89             assertThat(underTest.getAuthenticationMethod())
90                 .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
91         }
92 
93     @Test
94     fun authenticationMethod_none_whenLockscreenDisabled() =
95         testScope.runTest {
96             val authMethod by collectLastValue(underTest.authenticationMethod)
97             runCurrent()
98 
99             utils.authenticationRepository.apply {
100                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
101                 setLockscreenEnabled(false)
102             }
103 
104             assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
105             assertThat(underTest.getAuthenticationMethod())
106                 .isEqualTo(DomainLayerAuthenticationMethodModel.None)
107         }
108 
109     @Test
110     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
111         testScope.runTest {
112             val isUnlocked by collectLastValue(underTest.isUnlocked)
113             utils.authenticationRepository.apply {
114                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
115                 setLockscreenEnabled(false)
116                 // Toggle isUnlocked, twice.
117                 //
118                 // This is done because the underTest.isUnlocked flow doesn't receive values from
119                 // just changing the state above; the actual isUnlocked state needs to change to
120                 // cause the logic under test to "pick up" the current state again.
121                 //
122                 // It is done twice to make sure that we don't actually change the isUnlocked state
123                 // from what it originally was.
124                 setUnlocked(!utils.authenticationRepository.isUnlocked.value)
125                 runCurrent()
126                 setUnlocked(!utils.authenticationRepository.isUnlocked.value)
127                 runCurrent()
128             }
129 
130             assertThat(isUnlocked).isTrue()
131         }
132 
133     @Test
134     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
135         testScope.runTest {
136             utils.authenticationRepository.apply {
137                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
138                 setLockscreenEnabled(true)
139             }
140 
141             val isUnlocked by collectLastValue(underTest.isUnlocked)
142             assertThat(isUnlocked).isTrue()
143         }
144 
145     @Test
146     fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
147         testScope.runTest {
148             utils.authenticationRepository.apply {
149                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
150                 setLockscreenEnabled(true)
151             }
152             switchToScene(SceneKey.Lockscreen)
153 
154             val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
155             assertThat(canSwipeToDismiss).isTrue()
156         }
157 
158     @Test
159     fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
160         testScope.runTest {
161             utils.authenticationRepository.apply {
162                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
163                 setLockscreenEnabled(true)
164             }
165             switchToScene(SceneKey.Lockscreen)
166 
167             val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
168             assertThat(canSwipeToDismiss).isFalse()
169         }
170 
171     @Test
172     fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
173         testScope.runTest {
174             utils.authenticationRepository.apply {
175                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
176                 setLockscreenEnabled(true)
177             }
178             switchToScene(SceneKey.Lockscreen)
179             switchToScene(SceneKey.Gone)
180 
181             val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
182             assertThat(canSwipeToDismiss).isFalse()
183         }
184 
185     @Test
186     fun isAuthenticationRequired_lockedAndSecured_true() =
187         testScope.runTest {
188             utils.authenticationRepository.apply {
189                 setUnlocked(false)
190                 runCurrent()
191                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
192             }
193 
194             assertThat(underTest.isAuthenticationRequired()).isTrue()
195         }
196 
197     @Test
198     fun isAuthenticationRequired_lockedAndNotSecured_false() =
199         testScope.runTest {
200             utils.authenticationRepository.apply {
201                 setUnlocked(false)
202                 runCurrent()
203                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
204             }
205 
206             assertThat(underTest.isAuthenticationRequired()).isFalse()
207         }
208 
209     @Test
210     fun isAuthenticationRequired_unlockedAndSecured_false() =
211         testScope.runTest {
212             utils.authenticationRepository.apply {
213                 setUnlocked(true)
214                 runCurrent()
215                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
216             }
217 
218             assertThat(underTest.isAuthenticationRequired()).isFalse()
219         }
220 
221     @Test
222     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
223         testScope.runTest {
224             utils.authenticationRepository.apply {
225                 setUnlocked(true)
226                 runCurrent()
227                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
228             }
229 
230             assertThat(underTest.isAuthenticationRequired()).isFalse()
231         }
232 
233     @Test
234     fun authenticate_withCorrectPin_returnsTrue() =
235         testScope.runTest {
236             val isThrottled by collectLastValue(underTest.isThrottled)
237             utils.authenticationRepository.setAuthenticationMethod(
238                 DataLayerAuthenticationMethodModel.Pin
239             )
240             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
241             assertThat(isThrottled).isFalse()
242         }
243 
244     @Test
245     fun authenticate_withIncorrectPin_returnsFalse() =
246         testScope.runTest {
247             utils.authenticationRepository.setAuthenticationMethod(
248                 DataLayerAuthenticationMethodModel.Pin
249             )
250             assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
251         }
252 
253     @Test(expected = IllegalArgumentException::class)
254     fun authenticate_withEmptyPin_throwsException() =
255         testScope.runTest {
256             utils.authenticationRepository.setAuthenticationMethod(
257                 DataLayerAuthenticationMethodModel.Pin
258             )
259             underTest.authenticate(listOf())
260         }
261 
262     @Test
263     fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
264         testScope.runTest {
265             val pin = List(16) { 9 }
266             utils.authenticationRepository.apply {
267                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
268                 overrideCredential(pin)
269             }
270 
271             assertThat(underTest.authenticate(pin)).isTrue()
272         }
273 
274     @Test
275     fun authenticate_withCorrectTooLongPin_returnsFalse() =
276         testScope.runTest {
277             // Max pin length is 16 digits. To avoid issues with overflows, this test ensures
278             // that all pins > 16 decimal digits are rejected.
279 
280             // If the policy changes, there is work to do in SysUI.
281             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
282 
283             utils.authenticationRepository.setAuthenticationMethod(
284                 DataLayerAuthenticationMethodModel.Pin
285             )
286             assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
287         }
288 
289     @Test
290     fun authenticate_withCorrectPassword_returnsTrue() =
291         testScope.runTest {
292             val isThrottled by collectLastValue(underTest.isThrottled)
293             utils.authenticationRepository.setAuthenticationMethod(
294                 DataLayerAuthenticationMethodModel.Password
295             )
296 
297             assertThat(underTest.authenticate("password".toList())).isTrue()
298             assertThat(isThrottled).isFalse()
299         }
300 
301     @Test
302     fun authenticate_withIncorrectPassword_returnsFalse() =
303         testScope.runTest {
304             utils.authenticationRepository.setAuthenticationMethod(
305                 DataLayerAuthenticationMethodModel.Password
306             )
307 
308             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
309         }
310 
311     @Test
312     fun authenticate_withCorrectPattern_returnsTrue() =
313         testScope.runTest {
314             utils.authenticationRepository.setAuthenticationMethod(
315                 DataLayerAuthenticationMethodModel.Pattern
316             )
317 
318             assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
319         }
320 
321     @Test
322     fun authenticate_withIncorrectPattern_returnsFalse() =
323         testScope.runTest {
324             utils.authenticationRepository.setAuthenticationMethod(
325                 DataLayerAuthenticationMethodModel.Pattern
326             )
327 
328             assertThat(
329                     underTest.authenticate(
330                         listOf(
331                             AuthenticationPatternCoordinate(
332                                 x = 2,
333                                 y = 0,
334                             ),
335                             AuthenticationPatternCoordinate(
336                                 x = 2,
337                                 y = 1,
338                             ),
339                             AuthenticationPatternCoordinate(
340                                 x = 2,
341                                 y = 2,
342                             ),
343                         )
344                     )
345                 )
346                 .isFalse()
347         }
348 
349     @Test
350     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
351         testScope.runTest {
352             val isThrottled by collectLastValue(underTest.isThrottled)
353             utils.authenticationRepository.apply {
354                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
355                 setAutoConfirmEnabled(true)
356             }
357             assertThat(
358                     underTest.authenticate(
359                         FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
360                             removeLast()
361                         },
362                         tryAutoConfirm = true
363                     )
364                 )
365                 .isNull()
366             assertThat(isThrottled).isFalse()
367         }
368 
369     @Test
370     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
371         testScope.runTest {
372             val isUnlocked by collectLastValue(underTest.isUnlocked)
373             utils.authenticationRepository.apply {
374                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
375                 setAutoConfirmEnabled(true)
376             }
377             assertThat(
378                     underTest.authenticate(
379                         FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
380                         tryAutoConfirm = true
381                     )
382                 )
383                 .isFalse()
384             assertThat(isUnlocked).isFalse()
385         }
386 
387     @Test
388     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
389         testScope.runTest {
390             val isUnlocked by collectLastValue(underTest.isUnlocked)
391             utils.authenticationRepository.apply {
392                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
393                 setAutoConfirmEnabled(true)
394             }
395             assertThat(
396                     underTest.authenticate(
397                         FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
398                         tryAutoConfirm = true
399                     )
400                 )
401                 .isFalse()
402             assertThat(isUnlocked).isFalse()
403         }
404 
405     @Test
406     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
407         testScope.runTest {
408             val isUnlocked by collectLastValue(underTest.isUnlocked)
409             utils.authenticationRepository.apply {
410                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
411                 setAutoConfirmEnabled(true)
412             }
413             assertThat(
414                     underTest.authenticate(
415                         FakeAuthenticationRepository.DEFAULT_PIN,
416                         tryAutoConfirm = true
417                     )
418                 )
419                 .isTrue()
420             assertThat(isUnlocked).isTrue()
421         }
422 
423     @Test
424     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
425         testScope.runTest {
426             val isUnlocked by collectLastValue(underTest.isUnlocked)
427             utils.authenticationRepository.apply {
428                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
429                 setAutoConfirmEnabled(false)
430             }
431             assertThat(
432                     underTest.authenticate(
433                         FakeAuthenticationRepository.DEFAULT_PIN,
434                         tryAutoConfirm = true
435                     )
436                 )
437                 .isNull()
438             assertThat(isUnlocked).isFalse()
439         }
440 
441     @Test
442     fun tryAutoConfirm_withoutCorrectPassword_returnsNullAndHasNoEffects() =
443         testScope.runTest {
444             val isUnlocked by collectLastValue(underTest.isUnlocked)
445             utils.authenticationRepository.setAuthenticationMethod(
446                 DataLayerAuthenticationMethodModel.Password
447             )
448 
449             assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
450             assertThat(isUnlocked).isFalse()
451         }
452 
453     @Test
454     fun throttling() =
455         testScope.runTest {
456             val isUnlocked by collectLastValue(underTest.isUnlocked)
457             val throttling by collectLastValue(underTest.throttling)
458             val isThrottled by collectLastValue(underTest.isThrottled)
459             utils.authenticationRepository.setAuthenticationMethod(
460                 DataLayerAuthenticationMethodModel.Pin
461             )
462             underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
463             assertThat(isUnlocked).isTrue()
464             assertThat(isThrottled).isFalse()
465             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
466 
467             utils.authenticationRepository.setUnlocked(false)
468             assertThat(isUnlocked).isFalse()
469             assertThat(isThrottled).isFalse()
470             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
471 
472             // Make many wrong attempts, but just shy of what's needed to get throttled:
473             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) {
474                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
475                 assertThat(isUnlocked).isFalse()
476                 assertThat(isThrottled).isFalse()
477                 assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
478             }
479 
480             // Make one more wrong attempt, leading to throttling:
481             underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
482             assertThat(isUnlocked).isFalse()
483             assertThat(isThrottled).isTrue()
484             assertThat(throttling)
485                 .isEqualTo(
486                     AuthenticationThrottlingModel(
487                         failedAttemptCount =
488                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
489                         remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
490                     )
491                 )
492 
493             // Correct PIN, but throttled, so doesn't attempt it:
494             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isNull()
495             assertThat(isUnlocked).isFalse()
496             assertThat(isThrottled).isTrue()
497             assertThat(throttling)
498                 .isEqualTo(
499                     AuthenticationThrottlingModel(
500                         failedAttemptCount =
501                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
502                         remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS,
503                     )
504                 )
505 
506             // Move the clock forward to ALMOST skip the throttling, leaving one second to go:
507             val throttleTimeoutSec =
508                 FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds
509                     .toInt()
510             repeat(throttleTimeoutSec - 1) { time ->
511                 advanceTimeBy(1000)
512                 assertThat(isThrottled).isTrue()
513                 assertThat(throttling)
514                     .isEqualTo(
515                         AuthenticationThrottlingModel(
516                             failedAttemptCount =
517                                 FakeAuthenticationRepository
518                                     .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
519                             remainingMs =
520                                 ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds)
521                                     .toInt(),
522                         )
523                     )
524             }
525 
526             // Move the clock forward one more second, to completely finish the throttling period:
527             advanceTimeBy(1000)
528             assertThat(isUnlocked).isFalse()
529             assertThat(isThrottled).isFalse()
530             assertThat(throttling)
531                 .isEqualTo(
532                     AuthenticationThrottlingModel(
533                         failedAttemptCount =
534                             FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING,
535                         remainingMs = 0,
536                     )
537                 )
538 
539             // Correct PIN and no longer throttled so unlocks successfully:
540             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
541             assertThat(isUnlocked).isTrue()
542             assertThat(isThrottled).isFalse()
543             assertThat(throttling).isEqualTo(AuthenticationThrottlingModel())
544         }
545 
546     @Test
547     fun hintedPinLength_withoutAutoConfirm_isNull() =
548         testScope.runTest {
549             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
550             utils.authenticationRepository.apply {
551                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
552                 setAutoConfirmEnabled(false)
553             }
554 
555             assertThat(hintedPinLength).isNull()
556         }
557 
558     @Test
559     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
560         testScope.runTest {
561             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
562             utils.authenticationRepository.apply {
563                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
564                 overrideCredential(
565                     buildList {
566                         repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
567                     }
568                 )
569                 setAutoConfirmEnabled(true)
570             }
571 
572             assertThat(hintedPinLength).isNull()
573         }
574 
575     @Test
576     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
577         testScope.runTest {
578             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
579             utils.authenticationRepository.apply {
580                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
581                 setAutoConfirmEnabled(true)
582                 overrideCredential(
583                     buildList {
584                         repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
585                     }
586                 )
587             }
588 
589             assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
590         }
591 
592     @Test
593     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
594         testScope.runTest {
595             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
596             utils.authenticationRepository.apply {
597                 setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
598                 overrideCredential(
599                     buildList {
600                         repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
601                     }
602                 )
603                 setAutoConfirmEnabled(true)
604             }
605 
606             assertThat(hintedPinLength).isNull()
607         }
608 
609     @Test
610     fun isLockscreenDismissed() =
611         testScope.runTest {
612             val isLockscreenDismissed by collectLastValue(underTest.isLockscreenDismissed)
613             // Start on lockscreen.
614             switchToScene(SceneKey.Lockscreen)
615             assertThat(isLockscreenDismissed).isFalse()
616 
617             // The user swipes down to reveal shade.
618             switchToScene(SceneKey.Shade)
619             assertThat(isLockscreenDismissed).isFalse()
620 
621             // The user swipes down to reveal quick settings.
622             switchToScene(SceneKey.QuickSettings)
623             assertThat(isLockscreenDismissed).isFalse()
624 
625             // The user swipes up to go back to shade.
626             switchToScene(SceneKey.Shade)
627             assertThat(isLockscreenDismissed).isFalse()
628 
629             // The user swipes up to reveal bouncer.
630             switchToScene(SceneKey.Bouncer)
631             assertThat(isLockscreenDismissed).isFalse()
632 
633             // The user hits back to return to lockscreen.
634             switchToScene(SceneKey.Lockscreen)
635             assertThat(isLockscreenDismissed).isFalse()
636 
637             // The user swipes up to reveal bouncer.
638             switchToScene(SceneKey.Bouncer)
639             assertThat(isLockscreenDismissed).isFalse()
640 
641             // The user enters correct credentials and goes to gone.
642             switchToScene(SceneKey.Gone)
643             assertThat(isLockscreenDismissed).isTrue()
644 
645             // The user swipes down to reveal shade.
646             switchToScene(SceneKey.Shade)
647             assertThat(isLockscreenDismissed).isTrue()
648 
649             // The user swipes down to reveal quick settings.
650             switchToScene(SceneKey.QuickSettings)
651             assertThat(isLockscreenDismissed).isTrue()
652 
653             // The user swipes up to go back to shade.
654             switchToScene(SceneKey.Shade)
655             assertThat(isLockscreenDismissed).isTrue()
656 
657             // The user swipes up to go back to gone.
658             switchToScene(SceneKey.Gone)
659             assertThat(isLockscreenDismissed).isTrue()
660 
661             // The device goes to sleep, returning to the lockscreen.
662             switchToScene(SceneKey.Lockscreen)
663             assertThat(isLockscreenDismissed).isFalse()
664         }
665 
666     private fun TestScope.switchToScene(sceneKey: SceneKey) {
667         val model = SceneModel(sceneKey)
668         val loggingReason = "reason"
669         sceneInteractor.changeScene(model, loggingReason)
670         sceneInteractor.onSceneChanged(model, loggingReason)
671         runCurrent()
672     }
673 }
674