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