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 @file:OptIn(ExperimentalCoroutinesApi::class)
17 
18 package com.android.keyguard
19 
20 import android.content.res.Configuration
21 import android.hardware.biometrics.BiometricOverlayConstants
22 import android.media.AudioManager
23 import android.telephony.TelephonyManager
24 import android.testing.AndroidTestingRunner
25 import android.testing.TestableLooper.RunWithLooper
26 import android.testing.TestableResources
27 import android.view.Gravity
28 import android.view.LayoutInflater
29 import android.view.MotionEvent
30 import android.view.View
31 import android.view.WindowInsetsController
32 import android.widget.FrameLayout
33 import androidx.test.filters.SmallTest
34 import com.android.internal.logging.MetricsLogger
35 import com.android.internal.logging.UiEventLogger
36 import com.android.internal.widget.LockPatternUtils
37 import com.android.keyguard.KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback
38 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
39 import com.android.systemui.R
40 import com.android.systemui.SysuiTestCase
41 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
42 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
43 import com.android.systemui.biometrics.SideFpsController
44 import com.android.systemui.biometrics.SideFpsUiRequestSource
45 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
46 import com.android.systemui.classifier.FalsingA11yDelegate
47 import com.android.systemui.classifier.FalsingCollector
48 import com.android.systemui.flags.FakeFeatureFlags
49 import com.android.systemui.flags.Flags
50 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
51 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
52 import com.android.systemui.log.SessionTracker
53 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
54 import com.android.systemui.plugins.FalsingManager
55 import com.android.systemui.scene.SceneTestUtils
56 import com.android.systemui.scene.domain.interactor.SceneInteractor
57 import com.android.systemui.scene.shared.model.ObservableTransitionState
58 import com.android.systemui.scene.shared.model.SceneKey
59 import com.android.systemui.scene.shared.model.SceneModel
60 import com.android.systemui.statusbar.policy.ConfigurationController
61 import com.android.systemui.statusbar.policy.DeviceProvisionedController
62 import com.android.systemui.statusbar.policy.KeyguardStateController
63 import com.android.systemui.statusbar.policy.UserSwitcherController
64 import com.android.systemui.user.domain.interactor.UserInteractor
65 import com.android.systemui.util.kotlin.JavaAdapter
66 import com.android.systemui.util.mockito.any
67 import com.android.systemui.util.mockito.argThat
68 import com.android.systemui.util.mockito.argumentCaptor
69 import com.android.systemui.util.mockito.capture
70 import com.android.systemui.util.mockito.mock
71 import com.android.systemui.util.mockito.whenever
72 import com.android.systemui.util.settings.GlobalSettings
73 import com.google.common.truth.Truth
74 import java.util.Optional
75 import junit.framework.Assert
76 import kotlinx.coroutines.ExperimentalCoroutinesApi
77 import kotlinx.coroutines.flow.MutableStateFlow
78 import kotlinx.coroutines.flow.flowOf
79 import kotlinx.coroutines.test.runCurrent
80 import kotlinx.coroutines.test.runTest
81 import org.junit.Before
82 import org.junit.Test
83 import org.junit.runner.RunWith
84 import org.mockito.ArgumentCaptor
85 import org.mockito.ArgumentMatcher
86 import org.mockito.ArgumentMatchers.anyBoolean
87 import org.mockito.ArgumentMatchers.anyInt
88 import org.mockito.ArgumentMatchers.eq
89 import org.mockito.Captor
90 import org.mockito.Mock
91 import org.mockito.Mockito.atLeastOnce
92 import org.mockito.Mockito.clearInvocations
93 import org.mockito.Mockito.mock
94 import org.mockito.Mockito.never
95 import org.mockito.Mockito.spy
96 import org.mockito.Mockito.verify
97 import org.mockito.MockitoAnnotations
98 
99 @SmallTest
100 @RunWith(AndroidTestingRunner::class)
101 @RunWithLooper
102 class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
103 
104     @Mock private lateinit var view: KeyguardSecurityContainer
105     @Mock
106     private lateinit var adminSecondaryLockScreenControllerFactory:
107         AdminSecondaryLockScreenController.Factory
108     @Mock
109     private lateinit var adminSecondaryLockScreenController: AdminSecondaryLockScreenController
110     @Mock private lateinit var lockPatternUtils: LockPatternUtils
111     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
112     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
113     @Mock private lateinit var metricsLogger: MetricsLogger
114     @Mock private lateinit var uiEventLogger: UiEventLogger
115     @Mock private lateinit var keyguardStateController: KeyguardStateController
116     @Mock private lateinit var inputViewController: KeyguardInputViewController<KeyguardInputView>
117     @Mock private lateinit var windowInsetsController: WindowInsetsController
118     @Mock private lateinit var securityViewFlipper: KeyguardSecurityViewFlipper
119     @Mock private lateinit var viewFlipperController: KeyguardSecurityViewFlipperController
120     @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
121     @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<*>
122     @Mock private lateinit var keyguardMessageArea: BouncerKeyguardMessageArea
123     @Mock private lateinit var configurationController: ConfigurationController
124     @Mock private lateinit var emergencyButtonController: EmergencyButtonController
125     @Mock private lateinit var falsingCollector: FalsingCollector
126     @Mock private lateinit var falsingManager: FalsingManager
127     @Mock private lateinit var globalSettings: GlobalSettings
128     @Mock private lateinit var userSwitcherController: UserSwitcherController
129     @Mock private lateinit var sessionTracker: SessionTracker
130     @Mock private lateinit var keyguardViewController: KeyguardViewController
131     @Mock private lateinit var sideFpsController: SideFpsController
132     @Mock private lateinit var keyguardPasswordViewControllerMock: KeyguardPasswordViewController
133     @Mock private lateinit var falsingA11yDelegate: FalsingA11yDelegate
134     @Mock private lateinit var telephonyManager: TelephonyManager
135     @Mock private lateinit var viewMediatorCallback: ViewMediatorCallback
136     @Mock private lateinit var audioManager: AudioManager
137     @Mock private lateinit var userInteractor: UserInteractor
138     @Mock private lateinit var faceAuthAccessibilityDelegate: FaceAuthAccessibilityDelegate
139     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
140 
141     @Captor
142     private lateinit var swipeListenerArgumentCaptor:
143         ArgumentCaptor<KeyguardSecurityContainer.SwipeListener>
144     @Captor
145     private lateinit var onViewInflatedCallbackArgumentCaptor:
146         ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback>
147 
148     private lateinit var featureFlags: FakeFeatureFlags
149     private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
150     private lateinit var keyguardPasswordView: KeyguardPasswordView
151     private lateinit var testableResources: TestableResources
152     private lateinit var sceneTestUtils: SceneTestUtils
153     private lateinit var sceneInteractor: SceneInteractor
154     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
155     private lateinit var authenticationInteractor: AuthenticationInteractor
156     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
157 
158     private lateinit var underTest: KeyguardSecurityContainerController
159 
160     @Before
161     fun setUp() {
162         MockitoAnnotations.initMocks(this)
163 
164         testableResources = mContext.getOrCreateTestableResources()
165         testableResources.resources.configuration.orientation = Configuration.ORIENTATION_UNDEFINED
166         whenever(view.context).thenReturn(mContext)
167         whenever(view.resources).thenReturn(testableResources.resources)
168 
169         val lp = FrameLayout.LayoutParams(/* width=  */ 0, /* height= */ 0)
170         lp.gravity = 0
171         whenever(view.layoutParams).thenReturn(lp)
172 
173         whenever(adminSecondaryLockScreenControllerFactory.create(any()))
174             .thenReturn(adminSecondaryLockScreenController)
175         whenever(securityViewFlipper.windowInsetsController).thenReturn(windowInsetsController)
176         keyguardPasswordView =
177             spy(
178                 LayoutInflater.from(mContext).inflate(R.layout.keyguard_password_view, null)
179                     as KeyguardPasswordView
180             )
181         whenever(keyguardPasswordView.rootView).thenReturn(securityViewFlipper)
182         whenever<Any?>(keyguardPasswordView.requireViewById(R.id.bouncer_message_area))
183             .thenReturn(keyguardMessageArea)
184         whenever(messageAreaControllerFactory.create(any()))
185             .thenReturn(keyguardMessageAreaController)
186         whenever(keyguardPasswordView.windowInsetsController).thenReturn(windowInsetsController)
187         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN)
188         whenever(keyguardStateController.canDismissLockScreen()).thenReturn(true)
189         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(true)
190 
191         featureFlags = FakeFeatureFlags()
192         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
193         featureFlags.set(Flags.SCENE_CONTAINER, false)
194         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
195         featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
196 
197         keyguardPasswordViewController =
198             KeyguardPasswordViewController(
199                 keyguardPasswordView,
200                 keyguardUpdateMonitor,
201                 SecurityMode.Password,
202                 lockPatternUtils,
203                 null,
204                 messageAreaControllerFactory,
205                 null,
206                 null,
207                 emergencyButtonController,
208                 null,
209                 mock(),
210                 null,
211                 keyguardViewController,
212                 featureFlags
213             )
214 
215         whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
216         sceneTestUtils = SceneTestUtils(this)
217         sceneInteractor = sceneTestUtils.sceneInteractor()
218         keyguardTransitionInteractor =
219             KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
220                 .keyguardTransitionInteractor
221         sceneTransitionStateFlow =
222             MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
223         sceneInteractor.setTransitionState(sceneTransitionStateFlow)
224         authenticationInteractor =
225             sceneTestUtils.authenticationInteractor(
226                 repository = sceneTestUtils.authenticationRepository(),
227                 sceneInteractor = sceneInteractor
228             )
229 
230         underTest =
231             KeyguardSecurityContainerController(
232                 view,
233                 adminSecondaryLockScreenControllerFactory,
234                 lockPatternUtils,
235                 keyguardUpdateMonitor,
236                 keyguardSecurityModel,
237                 metricsLogger,
238                 uiEventLogger,
239                 keyguardStateController,
240                 viewFlipperController,
241                 configurationController,
242                 falsingCollector,
243                 falsingManager,
244                 userSwitcherController,
245                 featureFlags,
246                 globalSettings,
247                 sessionTracker,
248                 Optional.of(sideFpsController),
249                 falsingA11yDelegate,
250                 telephonyManager,
251                 viewMediatorCallback,
252                 audioManager,
253                 mock(),
254                 mock(),
255                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
256                 userInteractor,
257                 deviceProvisionedController,
258                 faceAuthAccessibilityDelegate,
259                 keyguardTransitionInteractor,
260                 { authenticationInteractor },
261             )
262     }
263 
264     @Test
265     fun onInitConfiguresViewMode() {
266         underTest.onInit()
267         verify(view)
268             .initMode(
269                 eq(KeyguardSecurityContainer.MODE_DEFAULT),
270                 eq(globalSettings),
271                 eq(falsingManager),
272                 eq(userSwitcherController),
273                 any(),
274                 eq(falsingA11yDelegate)
275             )
276     }
277 
278     @Test
279     fun setAccessibilityDelegate() {
280         verify(view).accessibilityDelegate = eq(faceAuthAccessibilityDelegate)
281     }
282 
283     @Test
284     fun showSecurityScreen_canInflateAllModes() {
285         val modes = SecurityMode.values()
286         for (mode in modes) {
287             whenever(inputViewController.securityMode).thenReturn(mode)
288             underTest.showSecurityScreen(mode)
289             if (mode == SecurityMode.Invalid) {
290                 verify(viewFlipperController, never()).getSecurityView(any(), any(), any())
291             } else {
292                 verify(viewFlipperController).getSecurityView(eq(mode), any(), any())
293             }
294         }
295     }
296 
297     @Test
298     fun onResourcesUpdate_callsThroughOnRotationChange() {
299         clearInvocations(view)
300 
301         // Rotation is the same, shouldn't cause an update
302         underTest.updateResources()
303         verify(view, never())
304             .initMode(
305                 eq(KeyguardSecurityContainer.MODE_DEFAULT),
306                 eq(globalSettings),
307                 eq(falsingManager),
308                 eq(userSwitcherController),
309                 any(),
310                 eq(falsingA11yDelegate)
311             )
312 
313         // Update rotation. Should trigger update
314         testableResources.resources.configuration.orientation = Configuration.ORIENTATION_LANDSCAPE
315         underTest.updateResources()
316         verify(view)
317             .initMode(
318                 eq(KeyguardSecurityContainer.MODE_DEFAULT),
319                 eq(globalSettings),
320                 eq(falsingManager),
321                 eq(userSwitcherController),
322                 any(),
323                 eq(falsingA11yDelegate)
324             )
325     }
326 
327     private fun touchDown() {
328         underTest.mGlobalTouchListener.onTouchEvent(
329             MotionEvent.obtain(
330                 /* downTime= */ 0,
331                 /* eventTime= */ 0,
332                 MotionEvent.ACTION_DOWN,
333                 /* x= */ 0f,
334                 /* y= */ 0f,
335                 /* metaState= */ 0
336             )
337         )
338     }
339 
340     @Test
341     fun onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
342         whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false)
343         touchDown()
344         verify(falsingCollector, never()).avoidGesture()
345         whenever(view.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true)
346         touchDown()
347         verify(falsingCollector).avoidGesture()
348     }
349 
350     @Test
351     fun showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() {
352         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
353         setupGetSecurityView(SecurityMode.Pattern)
354         underTest.showSecurityScreen(SecurityMode.Pattern)
355         verify(view)
356             .initMode(
357                 eq(KeyguardSecurityContainer.MODE_DEFAULT),
358                 eq(globalSettings),
359                 eq(falsingManager),
360                 eq(userSwitcherController),
361                 any(),
362                 eq(falsingA11yDelegate)
363             )
364     }
365 
366     @Test
367     fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() {
368         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
369         setupGetSecurityView(SecurityMode.Pattern)
370         verify(view)
371             .initMode(
372                 eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
373                 eq(globalSettings),
374                 eq(falsingManager),
375                 eq(userSwitcherController),
376                 any(),
377                 eq(falsingA11yDelegate)
378             )
379     }
380 
381     @Test
382     fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
383         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
384         setupGetSecurityView(SecurityMode.Password)
385         verify(view)
386             .initMode(
387                 eq(KeyguardSecurityContainer.MODE_DEFAULT),
388                 eq(globalSettings),
389                 eq(falsingManager),
390                 eq(userSwitcherController),
391                 any(),
392                 eq(falsingA11yDelegate)
393             )
394     }
395 
396     @Test
397     fun addUserSwitcherCallback() {
398         val captor = ArgumentCaptor.forClass(UserSwitcherCallback::class.java)
399         setupGetSecurityView(SecurityMode.Password)
400         verify(view)
401             .initMode(anyInt(), any(), any(), any(), captor.capture(), eq(falsingA11yDelegate))
402         captor.value.showUnlockToContinueMessage()
403         viewControllerImmediately
404         verify(keyguardPasswordViewControllerMock)
405             .showMessage(
406                 /* message= */ context.getString(R.string.keyguard_unlock_to_continue),
407                 /* colorState= */ null,
408                 /* animated= */ true
409             )
410     }
411 
412     @Test
413     fun addUserSwitchCallback() {
414         underTest.onViewAttached()
415         verify(userSwitcherController).addUserSwitchCallback(any())
416         underTest.onViewDetached()
417         verify(userSwitcherController).removeUserSwitchCallback(any())
418     }
419 
420     @Test
421     fun onBouncerVisibilityChanged_resetsScale() {
422         underTest.onBouncerVisibilityChanged(false)
423         verify(view).resetScale()
424     }
425 
426     @Test
427     fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() {
428         // GIVEN the current security method is SimPin
429         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
430         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
431             .thenReturn(false)
432         underTest.showSecurityScreen(SecurityMode.SimPin)
433 
434         // WHEN a request is made from the SimPin screens to show the next security method
435         whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN)
436         underTest.showNextSecurityScreenOrFinish(
437             /* authenticated= */ true,
438             TARGET_USER_ID,
439             /* bypassSecondaryLockScreen= */ true,
440             SecurityMode.SimPin
441         )
442 
443         // THEN the next security method of PIN is set, and the keyguard is not marked as done
444         verify(viewMediatorCallback, never()).keyguardDonePending(anyBoolean(), anyInt())
445         verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
446         Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN)
447     }
448 
449     @Test
450     fun showNextSecurityScreenOrFinish_DeviceNotSecure() {
451         // GIVEN the current security method is SimPin
452         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
453         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
454             .thenReturn(false)
455         underTest.showSecurityScreen(SecurityMode.SimPin)
456 
457         // WHEN a request is made from the SimPin screens to show the next security method
458         whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
459             .thenReturn(SecurityMode.None)
460         whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(true)
461         underTest.showNextSecurityScreenOrFinish(
462             /* authenticated= */ true,
463             TARGET_USER_ID,
464             /* bypassSecondaryLockScreen= */ true,
465             SecurityMode.SimPin
466         )
467 
468         // THEN the next security method of None will dismiss keyguard.
469         verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
470     }
471 
472     @Test
473     fun showNextSecurityScreenOrFinish_ignoresCallWhenSecurityMethodHasChanged() {
474         // GIVEN current security mode has been set to PIN
475         underTest.showSecurityScreen(SecurityMode.PIN)
476 
477         // WHEN a request comes from SimPin to dismiss the security screens
478         val keyguardDone =
479             underTest.showNextSecurityScreenOrFinish(
480                 /* authenticated= */ true,
481                 TARGET_USER_ID,
482                 /* bypassSecondaryLockScreen= */ true,
483                 SecurityMode.SimPin
484             )
485 
486         // THEN no action has happened, which will not dismiss the security screens
487         Truth.assertThat(keyguardDone).isEqualTo(false)
488         verify(keyguardUpdateMonitor, never()).getUserHasTrust(anyInt())
489     }
490 
491     @Test
492     fun showNextSecurityScreenOrFinish_SimPin_Swipe() {
493         // GIVEN the current security method is SimPin
494         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
495         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
496             .thenReturn(false)
497         underTest.showSecurityScreen(SecurityMode.SimPin)
498 
499         // WHEN a request is made from the SimPin screens to show the next security method
500         whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
501             .thenReturn(SecurityMode.None)
502         // WHEN security method is SWIPE
503         whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
504         underTest.showNextSecurityScreenOrFinish(
505             /* authenticated= */ true,
506             TARGET_USER_ID,
507             /* bypassSecondaryLockScreen= */ true,
508             SecurityMode.SimPin
509         )
510 
511         // THEN the next security method of None will dismiss keyguard.
512         verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
513     }
514     @Test
515     fun showNextSecurityScreenOrFinish_SimPin_Swipe_userNotSetup() {
516         // GIVEN the current security method is SimPin
517         whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false)
518         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID))
519             .thenReturn(false)
520         underTest.showSecurityScreen(SecurityMode.SimPin)
521 
522         // WHEN a request is made from the SimPin screens to show the next security method
523         whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID))
524             .thenReturn(SecurityMode.None)
525         // WHEN security method is SWIPE
526         whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false)
527         whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false)
528         underTest.showNextSecurityScreenOrFinish(
529             /* authenticated= */ true,
530             TARGET_USER_ID,
531             /* bypassSecondaryLockScreen= */ true,
532             SecurityMode.SimPin
533         )
534 
535         // THEN the next security method of None will dismiss keyguard.
536         verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
537     }
538 
539     @Test
540     fun onSwipeUp_whenFaceDetectionIsNotRunning_initiatesFaceAuth() {
541         val registeredSwipeListener = registeredSwipeListener
542         whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(false)
543         setupGetSecurityView(SecurityMode.Password)
544         registeredSwipeListener.onSwipeUp()
545         verify(keyguardUpdateMonitor).requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
546     }
547 
548     @Test
549     fun onSwipeUp_whenFaceDetectionIsRunning_doesNotInitiateFaceAuth() {
550         val registeredSwipeListener = registeredSwipeListener
551         whenever(keyguardUpdateMonitor.isFaceDetectionRunning).thenReturn(true)
552         registeredSwipeListener.onSwipeUp()
553         verify(keyguardUpdateMonitor, never())
554             .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
555     }
556 
557     @Test
558     fun onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
559         val registeredSwipeListener = registeredSwipeListener
560         whenever(
561                 keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
562             )
563             .thenReturn(true)
564         setupGetSecurityView(SecurityMode.Password)
565         clearInvocations(viewFlipperController)
566         registeredSwipeListener.onSwipeUp()
567         viewControllerImmediately
568         verify(keyguardPasswordViewControllerMock)
569             .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
570     }
571 
572     @Test
573     fun onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
574         val registeredSwipeListener = registeredSwipeListener
575         whenever(
576                 keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)
577             )
578             .thenReturn(false)
579         setupGetSecurityView(SecurityMode.Password)
580         registeredSwipeListener.onSwipeUp()
581         verify(keyguardPasswordViewControllerMock, never())
582             .showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true)
583     }
584 
585     @Test
586     fun onDensityOrFontScaleChanged() {
587         val configurationListenerArgumentCaptor =
588             ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
589         underTest.onViewAttached()
590         verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
591         clearInvocations(viewFlipperController)
592         configurationListenerArgumentCaptor.value.onDensityOrFontScaleChanged()
593         verify(viewFlipperController).clearViews()
594         verify(viewFlipperController)
595             .asynchronouslyInflateView(
596                 eq(SecurityMode.PIN),
597                 any(),
598                 onViewInflatedCallbackArgumentCaptor.capture()
599             )
600         onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
601         verify(view).onDensityOrFontScaleChanged()
602     }
603 
604     @Test
605     fun onThemeChanged() {
606         val configurationListenerArgumentCaptor =
607             ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
608         underTest.onViewAttached()
609         verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
610         configurationListenerArgumentCaptor.value.onThemeChanged()
611         verify(view).reloadColors()
612     }
613 
614     @Test
615     fun onUiModeChanged() {
616         val configurationListenerArgumentCaptor =
617             ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
618         underTest.onViewAttached()
619         verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
620         configurationListenerArgumentCaptor.value.onUiModeChanged()
621         verify(view).reloadColors()
622     }
623     @Test
624     fun onOrientationChanged_landscapeKeyguardFlagDisabled_blockReinflate() {
625         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false)
626 
627         // Run onOrientationChanged
628         val configurationListenerArgumentCaptor =
629             ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
630         underTest.onViewAttached()
631         verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
632         clearInvocations(viewFlipperController)
633         configurationListenerArgumentCaptor.value.onOrientationChanged(
634             Configuration.ORIENTATION_LANDSCAPE
635         )
636         // Verify view is reinflated when flag is on
637         verify(viewFlipperController, never()).clearViews()
638         verify(viewFlipperController, never())
639             .asynchronouslyInflateView(
640                 eq(SecurityMode.PIN),
641                 any(),
642                 onViewInflatedCallbackArgumentCaptor.capture()
643             )
644     }
645 
646     @Test
647     fun onOrientationChanged_landscapeKeyguardFlagEnabled_doesReinflate() {
648         featureFlags.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
649 
650         // Run onOrientationChanged
651         val configurationListenerArgumentCaptor =
652             ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
653         underTest.onViewAttached()
654         verify(configurationController).addCallback(configurationListenerArgumentCaptor.capture())
655         clearInvocations(viewFlipperController)
656         configurationListenerArgumentCaptor.value.onOrientationChanged(
657             Configuration.ORIENTATION_LANDSCAPE
658         )
659         // Verify view is reinflated when flag is on
660         verify(viewFlipperController).clearViews()
661         verify(viewFlipperController)
662             .asynchronouslyInflateView(
663                 eq(SecurityMode.PIN),
664                 any(),
665                 onViewInflatedCallbackArgumentCaptor.capture()
666             )
667     }
668 
669     @Test
670     fun hasDismissActions() {
671         Assert.assertFalse("Action not set yet", underTest.hasDismissActions())
672         underTest.setOnDismissAction(mock(), null /* cancelAction */)
673         Assert.assertTrue("Action should exist", underTest.hasDismissActions())
674     }
675 
676     @Test
677     fun willRunDismissFromKeyguardIsTrue() {
678         val action: OnDismissAction = mock()
679         whenever(action.willRunAnimationOnKeyguard()).thenReturn(true)
680         underTest.setOnDismissAction(action, null /* cancelAction */)
681         underTest.finish(false /* strongAuth */, 0 /* currentUser */)
682         Truth.assertThat(underTest.willRunDismissFromKeyguard()).isTrue()
683     }
684 
685     @Test
686     fun willRunDismissFromKeyguardIsFalse() {
687         val action: OnDismissAction = mock()
688         whenever(action.willRunAnimationOnKeyguard()).thenReturn(false)
689         underTest.setOnDismissAction(action, null /* cancelAction */)
690         underTest.finish(false /* strongAuth */, 0 /* currentUser */)
691         Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
692     }
693 
694     @Test
695     fun willRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
696         underTest.setOnDismissAction(null /* action */, null /* cancelAction */)
697         underTest.finish(false /* strongAuth */, 0 /* currentUser */)
698         Truth.assertThat(underTest.willRunDismissFromKeyguard()).isFalse()
699     }
700 
701     @Test
702     fun onStartingToHide() {
703         underTest.onStartingToHide()
704         verify(viewFlipperController)
705             .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
706         onViewInflatedCallbackArgumentCaptor.value.onViewInflated(inputViewController)
707         verify(inputViewController).onStartingToHide()
708     }
709 
710     @Test
711     fun gravityReappliedOnConfigurationChange() {
712         // Set initial gravity
713         testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
714         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
715 
716         // Kick off the initial pass...
717         underTest.onInit()
718         verify(view).layoutParams = any()
719         clearInvocations(view)
720 
721         // Now simulate a config change
722         testableResources.addOverride(
723             R.integer.keyguard_host_view_gravity,
724             Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
725         )
726         underTest.updateResources()
727         verify(view).layoutParams = any()
728     }
729 
730     @Test
731     fun gravityUsesOneHandGravityWhenApplicable() {
732         testableResources.addOverride(R.integer.keyguard_host_view_gravity, Gravity.CENTER)
733         testableResources.addOverride(
734             R.integer.keyguard_host_view_one_handed_gravity,
735             Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
736         )
737 
738         // Start disabled.
739         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, false)
740         underTest.onInit()
741         verify(view).layoutParams =
742             argThat(
743                 ArgumentMatcher { argument: FrameLayout.LayoutParams ->
744                     argument.gravity == Gravity.CENTER
745                 }
746                     as ArgumentMatcher<FrameLayout.LayoutParams>
747             )
748         clearInvocations(view)
749 
750         // And enable
751         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
752         underTest.updateResources()
753         verify(view).layoutParams =
754             argThat(
755                 ArgumentMatcher { argument: FrameLayout.LayoutParams ->
756                     argument.gravity == Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
757                 }
758                     as ArgumentMatcher<FrameLayout.LayoutParams>
759             )
760     }
761 
762     @Test
763     fun updateKeyguardPositionDelegatesToSecurityContainer() {
764         underTest.updateKeyguardPosition(1.0f)
765         verify(view).updatePositionByTouchX(1.0f)
766     }
767 
768     @Test
769     fun reinflateViewFlipper() {
770         val onViewInflatedCallback = KeyguardSecurityViewFlipperController.OnViewInflatedCallback {}
771         underTest.reinflateViewFlipper(onViewInflatedCallback)
772         verify(viewFlipperController).clearViews()
773         verify(viewFlipperController)
774             .asynchronouslyInflateView(any(), any(), eq(onViewInflatedCallback))
775     }
776 
777     @Test
778     fun sideFpsControllerShow() {
779         underTest.updateSideFpsVisibility(/* isVisible= */ true)
780         verify(sideFpsController)
781             .show(
782                 SideFpsUiRequestSource.PRIMARY_BOUNCER,
783                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD
784             )
785     }
786 
787     @Test
788     fun sideFpsControllerHide() {
789         underTest.updateSideFpsVisibility(/* isVisible= */ false)
790         verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
791     }
792 
793     @Test
794     fun setExpansion_setsAlpha() {
795         underTest.setExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
796         verify(view).alpha = 1f
797         verify(view).translationY = 0f
798     }
799 
800     @Test
801     fun dismissesKeyguard_whenSceneChangesToGone() =
802         sceneTestUtils.testScope.runTest {
803             featureFlags.set(Flags.SCENE_CONTAINER, true)
804 
805             // Upon init, we have never dismisses the keyguard.
806             underTest.onInit()
807             runCurrent()
808             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
809 
810             // Once the view is attached, we start listening but simply going to the bouncer scene
811             // is
812             // not enough to trigger a dismissal of the keyguard.
813             underTest.onViewAttached()
814             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
815             sceneTransitionStateFlow.value =
816                 ObservableTransitionState.Transition(
817                     SceneKey.Lockscreen,
818                     SceneKey.Bouncer,
819                     flowOf(.5f)
820                 )
821             runCurrent()
822             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
823             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
824             runCurrent()
825             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
826 
827             // While listening, going from the bouncer scene to the gone scene, does dismiss the
828             // keyguard.
829             sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
830             sceneTransitionStateFlow.value =
831                 ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
832             runCurrent()
833             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
834             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
835             runCurrent()
836             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
837 
838             // While listening, moving back to the lockscreen scene does not dismiss the keyguard
839             // again.
840             clearInvocations(viewMediatorCallback)
841             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
842             sceneTransitionStateFlow.value =
843                 ObservableTransitionState.Transition(
844                     SceneKey.Gone,
845                     SceneKey.Lockscreen,
846                     flowOf(.5f)
847                 )
848             runCurrent()
849             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
850             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
851             runCurrent()
852             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
853 
854             // While listening, moving back to the bouncer scene does not dismiss the keyguard
855             // again.
856             clearInvocations(viewMediatorCallback)
857             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
858             sceneTransitionStateFlow.value =
859                 ObservableTransitionState.Transition(
860                     SceneKey.Lockscreen,
861                     SceneKey.Bouncer,
862                     flowOf(.5f)
863                 )
864             runCurrent()
865             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
866             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
867             runCurrent()
868             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
869 
870             // Detaching the view stops listening, so moving from the bouncer scene to the gone
871             // scene
872             // does not dismiss the keyguard while we're not listening.
873             underTest.onViewDetached()
874             sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
875             sceneTransitionStateFlow.value =
876                 ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
877             runCurrent()
878             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
879             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
880             runCurrent()
881             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
882 
883             // While not listening, moving to the lockscreen does not dismiss the keyguard.
884             sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
885             sceneTransitionStateFlow.value =
886                 ObservableTransitionState.Transition(
887                     SceneKey.Gone,
888                     SceneKey.Lockscreen,
889                     flowOf(.5f)
890                 )
891             runCurrent()
892             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
893             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
894             runCurrent()
895             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
896 
897             // While not listening, moving to the bouncer does not dismiss the keyguard.
898             sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
899             sceneTransitionStateFlow.value =
900                 ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
901             runCurrent()
902             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
903             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
904             runCurrent()
905             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
906 
907             // Reattaching the view starts listening again so moving from the bouncer scene to the
908             // gone scene now does dismiss the keyguard again, this time from lockscreen.
909             underTest.onViewAttached()
910             sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
911             sceneTransitionStateFlow.value =
912                 ObservableTransitionState.Transition(
913                     SceneKey.Lockscreen,
914                     SceneKey.Gone,
915                     flowOf(.5f)
916                 )
917             runCurrent()
918             sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
919             sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
920             runCurrent()
921             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
922         }
923 
924     @Test
925     fun testResetUserSwitcher() {
926         val userSwitcher = mock(View::class.java)
927         whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher))
928             .thenReturn(userSwitcher)
929 
930         underTest.prepareToShow()
931         verify(userSwitcher).setAlpha(0f)
932     }
933 
934     @Test
935     fun testOnUserSwitched() {
936         val userSwitchCallbackArgumentCaptor =
937             argumentCaptor<UserSwitcherController.UserSwitchCallback>()
938         underTest.onViewAttached()
939         verify(userSwitcherController)
940             .addUserSwitchCallback(capture(userSwitchCallbackArgumentCaptor))
941         userSwitchCallbackArgumentCaptor.value.onUserSwitched()
942         verify(viewFlipperController).asynchronouslyInflateView(any(), any(), any())
943     }
944 
945     private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
946         get() {
947             underTest.onViewAttached()
948             verify(view).setSwipeListener(swipeListenerArgumentCaptor.capture())
949             return swipeListenerArgumentCaptor.value
950         }
951 
952     private fun setupGetSecurityView(securityMode: SecurityMode) {
953         underTest.showSecurityScreen(securityMode)
954         viewControllerImmediately
955     }
956 
957     private val viewControllerImmediately: Unit
958         get() {
959             verify(viewFlipperController, atLeastOnce())
960                 .getSecurityView(any(), any(), onViewInflatedCallbackArgumentCaptor.capture())
961             @Suppress("UNCHECKED_CAST")
962             onViewInflatedCallbackArgumentCaptor.value.onViewInflated(
963                 keyguardPasswordViewControllerMock as KeyguardInputViewController<KeyguardInputView>
964             )
965         }
966 
967     companion object {
968         private const val TARGET_USER_ID = 100
969     }
970 }
971