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.ui.viewmodel
18 
19 import android.app.admin.DevicePolicyManager
20 import android.content.Intent
21 import android.os.UserHandle
22 import androidx.test.filters.SmallTest
23 import com.android.internal.logging.testing.UiEventLoggerFake
24 import com.android.internal.widget.LockPatternUtils
25 import com.android.systemui.R
26 import com.android.systemui.SysuiTestCase
27 import com.android.systemui.animation.DialogLaunchAnimator
28 import com.android.systemui.animation.Expandable
29 import com.android.systemui.broadcast.BroadcastDispatcher
30 import com.android.systemui.common.shared.model.Icon
31 import com.android.systemui.coroutines.collectLastValue
32 import com.android.systemui.dock.DockManagerFake
33 import com.android.systemui.doze.util.BurnInHelperWrapper
34 import com.android.systemui.flags.FakeFeatureFlags
35 import com.android.systemui.flags.Flags
36 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
37 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
38 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
39 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
40 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
41 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
42 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
43 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
44 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
45 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
46 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
47 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
48 import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
49 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
50 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
51 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
52 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
53 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
54 import com.android.systemui.plugins.ActivityStarter
55 import com.android.systemui.settings.UserFileManager
56 import com.android.systemui.settings.UserTracker
57 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
58 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
59 import com.android.systemui.statusbar.policy.KeyguardStateController
60 import com.android.systemui.util.FakeSharedPreferences
61 import com.android.systemui.util.mockito.any
62 import com.android.systemui.util.mockito.mock
63 import com.android.systemui.util.mockito.whenever
64 import com.android.systemui.util.settings.FakeSettings
65 import com.google.common.truth.Truth.assertThat
66 import kotlin.math.max
67 import kotlin.math.min
68 import kotlinx.coroutines.ExperimentalCoroutinesApi
69 import kotlinx.coroutines.flow.map
70 import kotlinx.coroutines.test.StandardTestDispatcher
71 import kotlinx.coroutines.test.TestScope
72 import kotlinx.coroutines.test.runTest
73 import org.junit.Before
74 import org.junit.Test
75 import org.junit.runner.RunWith
76 import org.junit.runners.JUnit4
77 import org.mockito.ArgumentMatchers.anyInt
78 import org.mockito.ArgumentMatchers.anyString
79 import org.mockito.Mock
80 import org.mockito.Mockito
81 import org.mockito.Mockito.verifyZeroInteractions
82 import org.mockito.MockitoAnnotations
83 
84 @OptIn(ExperimentalCoroutinesApi::class)
85 @SmallTest
86 @RunWith(JUnit4::class)
87 class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
88 
89     @Mock private lateinit var expandable: Expandable
90     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
91     @Mock private lateinit var lockPatternUtils: LockPatternUtils
92     @Mock private lateinit var keyguardStateController: KeyguardStateController
93     @Mock private lateinit var userTracker: UserTracker
94     @Mock private lateinit var activityStarter: ActivityStarter
95     @Mock private lateinit var launchAnimator: DialogLaunchAnimator
96     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
97     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
98     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
99     @Mock private lateinit var accessibilityManager: AccessibilityManagerWrapper
100 
101     private lateinit var underTest: KeyguardBottomAreaViewModel
102 
103     private lateinit var testScope: TestScope
104     private lateinit var repository: FakeKeyguardRepository
105     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
106     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
107     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
108     private lateinit var dockManager: DockManagerFake
109     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
110 
111     @Before
112     fun setUp() {
113         MockitoAnnotations.initMocks(this)
114 
115         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
116         overrideResource(
117             R.array.config_keyguardQuickAffordanceDefaults,
118             arrayOf(
119                 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + ":" +
120                     BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
121                 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END + ":" +
122                     BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
123             )
124         )
125 
126         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
127             .thenReturn(RETURNED_BURN_IN_OFFSET)
128 
129         homeControlsQuickAffordanceConfig =
130             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
131         quickAccessWalletAffordanceConfig =
132             FakeKeyguardQuickAffordanceConfig(
133                 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
134             )
135         qrCodeScannerAffordanceConfig =
136             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
137         dockManager = DockManagerFake()
138         biometricSettingsRepository = FakeBiometricSettingsRepository()
139         val featureFlags =
140             FakeFeatureFlags().apply {
141                 set(Flags.FACE_AUTH_REFACTOR, true)
142                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
143                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
144             }
145 
146         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
147         val keyguardInteractor = withDeps.keyguardInteractor
148         repository = withDeps.repository
149 
150         whenever(userTracker.userHandle).thenReturn(mock())
151         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
152             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
153         val testDispatcher = StandardTestDispatcher()
154         testScope = TestScope(testDispatcher)
155         val localUserSelectionManager =
156             KeyguardQuickAffordanceLocalUserSelectionManager(
157                 context = context,
158                 userFileManager =
159                     mock<UserFileManager>().apply {
160                         whenever(
161                                 getSharedPreferences(
162                                     anyString(),
163                                     anyInt(),
164                                     anyInt(),
165                                 )
166                             )
167                             .thenReturn(FakeSharedPreferences())
168                     },
169                 userTracker = userTracker,
170                 broadcastDispatcher = fakeBroadcastDispatcher,
171             )
172         val remoteUserSelectionManager =
173             KeyguardQuickAffordanceRemoteUserSelectionManager(
174                 scope = testScope.backgroundScope,
175                 userTracker = userTracker,
176                 clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
177                 userHandle = UserHandle.SYSTEM,
178             )
179         val quickAffordanceRepository =
180             KeyguardQuickAffordanceRepository(
181                 appContext = context,
182                 scope = testScope.backgroundScope,
183                 localUserSelectionManager = localUserSelectionManager,
184                 remoteUserSelectionManager = remoteUserSelectionManager,
185                 userTracker = userTracker,
186                 legacySettingSyncer =
187                     KeyguardQuickAffordanceLegacySettingSyncer(
188                         scope = testScope.backgroundScope,
189                         backgroundDispatcher = testDispatcher,
190                         secureSettings = FakeSettings(),
191                         selectionsManager = localUserSelectionManager,
192                     ),
193                 configs =
194                     setOf(
195                         homeControlsQuickAffordanceConfig,
196                         quickAccessWalletAffordanceConfig,
197                         qrCodeScannerAffordanceConfig,
198                     ),
199                 dumpManager = mock(),
200                 userHandle = UserHandle.SYSTEM,
201             )
202         val keyguardLongPressInteractor =
203             KeyguardLongPressInteractor(
204                 appContext = mContext,
205                 scope = testScope.backgroundScope,
206                 transitionInteractor =
207                     KeyguardTransitionInteractorFactory.create(
208                             scope = TestScope().backgroundScope,
209                         )
210                         .keyguardTransitionInteractor,
211                 repository = repository,
212                 logger = UiEventLoggerFake(),
213                 featureFlags = featureFlags,
214                 broadcastDispatcher = broadcastDispatcher,
215                 accessibilityManager = accessibilityManager,
216             )
217         underTest =
218             KeyguardBottomAreaViewModel(
219                 keyguardInteractor = keyguardInteractor,
220                 quickAffordanceInteractor =
221                     KeyguardQuickAffordanceInteractor(
222                         keyguardInteractor = keyguardInteractor,
223                         lockPatternUtils = lockPatternUtils,
224                         keyguardStateController = keyguardStateController,
225                         userTracker = userTracker,
226                         activityStarter = activityStarter,
227                         featureFlags = featureFlags,
228                         repository = { quickAffordanceRepository },
229                         launchAnimator = launchAnimator,
230                         logger = logger,
231                         devicePolicyManager = devicePolicyManager,
232                         dockManager = dockManager,
233                         biometricSettingsRepository = biometricSettingsRepository,
234                         backgroundDispatcher = testDispatcher,
235                         appContext = mContext,
236                     ),
237                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
238                 burnInHelperWrapper = burnInHelperWrapper,
239                 longPressViewModel =
240                     KeyguardLongPressViewModel(
241                         interactor = keyguardLongPressInteractor,
242                     ),
243                 settingsMenuViewModel =
244                     KeyguardSettingsMenuViewModel(
245                         interactor = keyguardLongPressInteractor,
246                     ),
247             )
248     }
249 
250     @Test
251     fun startButton_present_visibleModel_startsActivityOnClick() =
252         testScope.runTest {
253             repository.setKeyguardShowing(true)
254             val latest = collectLastValue(underTest.startButton)
255 
256             val testConfig =
257                 TestConfig(
258                     isVisible = true,
259                     isClickable = true,
260                     isActivated = true,
261                     icon = mock(),
262                     canShowWhileLocked = false,
263                     intent = Intent("action"),
264                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
265                 )
266             val configKey =
267                 setUpQuickAffordanceModel(
268                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
269                     testConfig = testConfig,
270                 )
271 
272             assertQuickAffordanceViewModel(
273                 viewModel = latest(),
274                 testConfig = testConfig,
275                 configKey = configKey,
276             )
277         }
278 
279     @Test
280     fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
281         testScope.runTest {
282             whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
283                 .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
284             repository.setKeyguardShowing(true)
285             val latest by collectLastValue(underTest.startButton)
286 
287             val testConfig =
288                 TestConfig(
289                     isVisible = true,
290                     isClickable = true,
291                     isActivated = true,
292                     icon = mock(),
293                     canShowWhileLocked = false,
294                     intent = Intent("action"),
295                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
296                 )
297             val configKey =
298                 setUpQuickAffordanceModel(
299                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
300                     testConfig = testConfig,
301                 )
302 
303             assertQuickAffordanceViewModel(
304                 viewModel = latest,
305                 testConfig =
306                     TestConfig(
307                         isVisible = false,
308                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
309                     ),
310                 configKey = configKey,
311             )
312         }
313 
314     @Test
315     fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
316         testScope.runTest {
317             underTest.enablePreviewMode(
318                 initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
319                 shouldHighlightSelectedAffordance = true,
320             )
321             repository.setKeyguardShowing(false)
322             val latest = collectLastValue(underTest.startButton)
323 
324             val icon: Icon = mock()
325             val configKey =
326                 setUpQuickAffordanceModel(
327                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
328                     testConfig =
329                         TestConfig(
330                             isVisible = true,
331                             isClickable = true,
332                             isActivated = true,
333                             icon = icon,
334                             canShowWhileLocked = false,
335                             intent = Intent("action"),
336                             slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
337                         ),
338                 )
339 
340             assertQuickAffordanceViewModel(
341                 viewModel = latest(),
342                 testConfig =
343                     TestConfig(
344                         isVisible = true,
345                         isClickable = false,
346                         isActivated = false,
347                         icon = icon,
348                         canShowWhileLocked = false,
349                         intent = Intent("action"),
350                         isSelected = true,
351                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
352                     ),
353                 configKey = configKey,
354             )
355             assertThat(latest()?.isSelected).isTrue()
356         }
357 
358     @Test
359     fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
360         testScope.runTest {
361             underTest.enablePreviewMode(
362                 initiallySelectedSlotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
363                 shouldHighlightSelectedAffordance = true,
364             )
365             repository.setKeyguardShowing(false)
366             val startButton = collectLastValue(underTest.startButton)
367             val endButton = collectLastValue(underTest.endButton)
368 
369             val icon: Icon = mock()
370             setUpQuickAffordanceModel(
371                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
372                 testConfig =
373                     TestConfig(
374                         isVisible = true,
375                         isClickable = true,
376                         isActivated = true,
377                         icon = icon,
378                         canShowWhileLocked = false,
379                         intent = Intent("action"),
380                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
381                     ),
382             )
383             val configKey =
384                 setUpQuickAffordanceModel(
385                     position = KeyguardQuickAffordancePosition.BOTTOM_END,
386                     testConfig =
387                         TestConfig(
388                             isVisible = true,
389                             isClickable = true,
390                             isActivated = true,
391                             icon = icon,
392                             canShowWhileLocked = false,
393                             intent = Intent("action"),
394                             slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
395                         ),
396                 )
397 
398             assertQuickAffordanceViewModel(
399                 viewModel = endButton(),
400                 testConfig =
401                     TestConfig(
402                         isVisible = true,
403                         isClickable = false,
404                         isActivated = false,
405                         icon = icon,
406                         canShowWhileLocked = false,
407                         intent = Intent("action"),
408                         isDimmed = true,
409                         slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
410                     ),
411                 configKey = configKey,
412             )
413         }
414 
415     @Test
416     fun endButton_present_visibleModel_doNothingOnClick() =
417         testScope.runTest {
418             repository.setKeyguardShowing(true)
419             val latest = collectLastValue(underTest.endButton)
420 
421             val config =
422                 TestConfig(
423                     isVisible = true,
424                     isClickable = true,
425                     icon = mock(),
426                     canShowWhileLocked = false,
427                     intent =
428                         null, // This will cause it to tell the system that the click was handled.
429                     slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
430                 )
431             val configKey =
432                 setUpQuickAffordanceModel(
433                     position = KeyguardQuickAffordancePosition.BOTTOM_END,
434                     testConfig = config,
435                 )
436 
437             assertQuickAffordanceViewModel(
438                 viewModel = latest(),
439                 testConfig = config,
440                 configKey = configKey,
441             )
442         }
443 
444     @Test
445     fun startButton_notPresent_modelIsHidden() =
446         testScope.runTest {
447             val latest = collectLastValue(underTest.startButton)
448 
449             val config =
450                 TestConfig(
451                     isVisible = false,
452                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
453                 )
454             val configKey =
455                 setUpQuickAffordanceModel(
456                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
457                     testConfig = config,
458                 )
459 
460             assertQuickAffordanceViewModel(
461                 viewModel = latest(),
462                 testConfig = config,
463                 configKey = configKey,
464             )
465         }
466 
467     @Test
468     fun animateButtonReveal() =
469         testScope.runTest {
470             repository.setKeyguardShowing(true)
471             val testConfig =
472                 TestConfig(
473                     isVisible = true,
474                     isClickable = true,
475                     icon = mock(),
476                     canShowWhileLocked = false,
477                     intent = Intent("action"),
478                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
479                 )
480 
481             setUpQuickAffordanceModel(
482                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
483                 testConfig = testConfig,
484             )
485 
486             val value = collectLastValue(underTest.startButton.map { it.animateReveal })
487 
488             assertThat(value()).isFalse()
489             repository.setAnimateDozingTransitions(true)
490             assertThat(value()).isTrue()
491             repository.setAnimateDozingTransitions(false)
492             assertThat(value()).isFalse()
493         }
494 
495     @Test
496     fun isOverlayContainerVisible() =
497         testScope.runTest {
498             val value = collectLastValue(underTest.isOverlayContainerVisible)
499 
500             assertThat(value()).isTrue()
501             repository.setIsDozing(true)
502             assertThat(value()).isFalse()
503             repository.setIsDozing(false)
504             assertThat(value()).isTrue()
505         }
506 
507     @Test
508     fun alpha() =
509         testScope.runTest {
510             val value = collectLastValue(underTest.alpha)
511 
512             assertThat(value()).isEqualTo(1f)
513             repository.setBottomAreaAlpha(0.1f)
514             assertThat(value()).isEqualTo(0.1f)
515             repository.setBottomAreaAlpha(0.5f)
516             assertThat(value()).isEqualTo(0.5f)
517             repository.setBottomAreaAlpha(0.2f)
518             assertThat(value()).isEqualTo(0.2f)
519             repository.setBottomAreaAlpha(0f)
520             assertThat(value()).isEqualTo(0f)
521         }
522 
523     @Test
524     fun alpha_inPreviewMode_doesNotChange() =
525         testScope.runTest {
526             underTest.enablePreviewMode(
527                 initiallySelectedSlotId = null,
528                 shouldHighlightSelectedAffordance = false,
529             )
530             val value = collectLastValue(underTest.alpha)
531 
532             assertThat(value()).isEqualTo(1f)
533             repository.setBottomAreaAlpha(0.1f)
534             assertThat(value()).isEqualTo(1f)
535             repository.setBottomAreaAlpha(0.5f)
536             assertThat(value()).isEqualTo(1f)
537             repository.setBottomAreaAlpha(0.2f)
538             assertThat(value()).isEqualTo(1f)
539             repository.setBottomAreaAlpha(0f)
540             assertThat(value()).isEqualTo(1f)
541         }
542 
543     @Test
544     fun isClickable_trueWhenAlphaAtThreshold() =
545         testScope.runTest {
546             repository.setKeyguardShowing(true)
547             repository.setBottomAreaAlpha(
548                 KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
549             )
550 
551             val testConfig =
552                 TestConfig(
553                     isVisible = true,
554                     isClickable = true,
555                     icon = mock(),
556                     canShowWhileLocked = false,
557                     intent = Intent("action"),
558                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
559                 )
560             val configKey =
561                 setUpQuickAffordanceModel(
562                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
563                     testConfig = testConfig,
564                 )
565 
566             val latest = collectLastValue(underTest.startButton)
567 
568             assertQuickAffordanceViewModel(
569                 viewModel = latest(),
570                 testConfig = testConfig,
571                 configKey = configKey,
572             )
573         }
574 
575     @Test
576     fun isClickable_trueWhenAlphaAboveThreshold() =
577         testScope.runTest {
578             repository.setKeyguardShowing(true)
579             val latest = collectLastValue(underTest.startButton)
580             repository.setBottomAreaAlpha(
581                 min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
582             )
583 
584             val testConfig =
585                 TestConfig(
586                     isVisible = true,
587                     isClickable = true,
588                     icon = mock(),
589                     canShowWhileLocked = false,
590                     intent = Intent("action"),
591                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
592                 )
593             val configKey =
594                 setUpQuickAffordanceModel(
595                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
596                     testConfig = testConfig,
597                 )
598 
599             assertQuickAffordanceViewModel(
600                 viewModel = latest(),
601                 testConfig = testConfig,
602                 configKey = configKey,
603             )
604         }
605 
606     @Test
607     fun isClickable_falseWhenAlphaBelowThreshold() =
608         testScope.runTest {
609             repository.setKeyguardShowing(true)
610             val latest = collectLastValue(underTest.startButton)
611             repository.setBottomAreaAlpha(
612                 max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
613             )
614 
615             val testConfig =
616                 TestConfig(
617                     isVisible = true,
618                     isClickable = false,
619                     icon = mock(),
620                     canShowWhileLocked = false,
621                     intent = Intent("action"),
622                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
623                 )
624             val configKey =
625                 setUpQuickAffordanceModel(
626                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
627                     testConfig = testConfig,
628                 )
629 
630             assertQuickAffordanceViewModel(
631                 viewModel = latest(),
632                 testConfig = testConfig,
633                 configKey = configKey,
634             )
635         }
636 
637     @Test
638     fun isClickable_falseWhenAlphaAtZero() =
639         testScope.runTest {
640             repository.setKeyguardShowing(true)
641             val latest = collectLastValue(underTest.startButton)
642             repository.setBottomAreaAlpha(0f)
643 
644             val testConfig =
645                 TestConfig(
646                     isVisible = true,
647                     isClickable = false,
648                     icon = mock(),
649                     canShowWhileLocked = false,
650                     intent = Intent("action"),
651                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
652                 )
653             val configKey =
654                 setUpQuickAffordanceModel(
655                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
656                     testConfig = testConfig,
657                 )
658 
659             assertQuickAffordanceViewModel(
660                 viewModel = latest(),
661                 testConfig = testConfig,
662                 configKey = configKey,
663             )
664         }
665 
666     private suspend fun setUpQuickAffordanceModel(
667         position: KeyguardQuickAffordancePosition,
668         testConfig: TestConfig,
669     ): String {
670         val config =
671             when (position) {
672                 KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
673                 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
674             }
675 
676         val lockScreenState =
677             if (testConfig.isVisible) {
678                 if (testConfig.intent != null) {
679                     config.onTriggeredResult =
680                         KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
681                             intent = testConfig.intent,
682                             canShowWhileLocked = testConfig.canShowWhileLocked,
683                         )
684                 }
685                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
686                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
687                     activationState =
688                         when (testConfig.isActivated) {
689                             true -> ActivationState.Active
690                             false -> ActivationState.Inactive
691                         }
692                 )
693             } else {
694                 KeyguardQuickAffordanceConfig.LockScreenState.Hidden
695             }
696         config.setState(lockScreenState)
697 
698         return "${position.toSlotId()}::${config.key}"
699     }
700 
701     private fun assertQuickAffordanceViewModel(
702         viewModel: KeyguardQuickAffordanceViewModel?,
703         testConfig: TestConfig,
704         configKey: String,
705     ) {
706         checkNotNull(viewModel)
707         assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
708         assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
709         assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
710         assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
711         assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
712         assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
713         if (testConfig.isVisible) {
714             assertThat(viewModel.icon).isEqualTo(testConfig.icon)
715             viewModel.onClicked.invoke(
716                 KeyguardQuickAffordanceViewModel.OnClickedParameters(
717                     configKey = configKey,
718                     expandable = expandable,
719                     slotId = viewModel.slotId,
720                 )
721             )
722             if (testConfig.intent != null) {
723                 assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
724             } else {
725                 verifyZeroInteractions(activityStarter)
726             }
727         } else {
728             assertThat(viewModel.isVisible).isFalse()
729         }
730     }
731 
732     private data class TestConfig(
733         val isVisible: Boolean,
734         val isClickable: Boolean = false,
735         val isActivated: Boolean = false,
736         val icon: Icon? = null,
737         val canShowWhileLocked: Boolean = false,
738         val intent: Intent? = null,
739         val isSelected: Boolean = false,
740         val isDimmed: Boolean = false,
741         val slotId: String = ""
742     ) {
743         init {
744             check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
745         }
746     }
747 
748     companion object {
749         private const val DEFAULT_BURN_IN_OFFSET = 5
750         private const val RETURNED_BURN_IN_OFFSET = 3
751     }
752 }
753