1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 package com.android.systemui.keyguard.ui.viewmodel
19 
20 import android.app.admin.DevicePolicyManager
21 import android.content.Intent
22 import android.os.UserHandle
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.SmallTest
25 import com.android.internal.widget.LockPatternUtils
26 import com.android.systemui.Flags as AConfigFlags
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.common.shared.model.Icon
31 import com.android.systemui.coroutines.collectLastValue
32 import com.android.systemui.dock.DockManagerFake
33 import com.android.systemui.flags.FakeFeatureFlags
34 import com.android.systemui.flags.Flags
35 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
36 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
37 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory
38 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
39 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer
40 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager
41 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager
42 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
43 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
44 import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
45 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
46 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
47 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
48 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
49 import com.android.systemui.keyguard.shared.model.KeyguardState
50 import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
51 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
52 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
53 import com.android.systemui.kosmos.testDispatcher
54 import com.android.systemui.kosmos.testScope
55 import com.android.systemui.plugins.ActivityStarter
56 import com.android.systemui.res.R
57 import com.android.systemui.scene.domain.interactor.sceneInteractor
58 import com.android.systemui.settings.UserFileManager
59 import com.android.systemui.settings.UserTracker
60 import com.android.systemui.shade.domain.interactor.ShadeInteractor
61 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
62 import com.android.systemui.statusbar.policy.KeyguardStateController
63 import com.android.systemui.testKosmos
64 import com.android.systemui.util.FakeSharedPreferences
65 import com.android.systemui.util.mockito.mock
66 import com.android.systemui.util.mockito.whenever
67 import com.android.systemui.util.settings.FakeSettings
68 import com.google.common.truth.Truth
69 import kotlin.math.min
70 import kotlin.test.assertEquals
71 import kotlinx.coroutines.ExperimentalCoroutinesApi
72 import kotlinx.coroutines.flow.MutableStateFlow
73 import kotlinx.coroutines.flow.emptyFlow
74 import kotlinx.coroutines.flow.map
75 import kotlinx.coroutines.test.runTest
76 import org.junit.Before
77 import org.junit.Test
78 import org.junit.runner.RunWith
79 import org.mockito.ArgumentMatchers
80 import org.mockito.Mock
81 import org.mockito.Mockito
82 import org.mockito.MockitoAnnotations
83 
84 @OptIn(ExperimentalCoroutinesApi::class)
85 @SmallTest
86 @RunWith(AndroidJUnit4::class)
87 class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
88 
89     @Mock private lateinit var activityStarter: ActivityStarter
90     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
91     @Mock private lateinit var expandable: Expandable
92     @Mock private lateinit var userTracker: UserTracker
93     @Mock private lateinit var lockPatternUtils: LockPatternUtils
94     @Mock private lateinit var keyguardStateController: KeyguardStateController
95     @Mock private lateinit var launchAnimator: DialogTransitionAnimator
96     @Mock private lateinit var logger: KeyguardQuickAffordancesMetricsLogger
97     @Mock private lateinit var shadeInteractor: ShadeInteractor
98     @Mock
99     private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
100     @Mock
101     private lateinit var dozingToLockscreenTransitionViewModel:
102         DozingToLockscreenTransitionViewModel
103     @Mock
104     private lateinit var dreamingHostedToLockscreenTransitionViewModel:
105         DreamingHostedToLockscreenTransitionViewModel
106     @Mock
107     private lateinit var dreamingToLockscreenTransitionViewModel:
108         DreamingToLockscreenTransitionViewModel
109     @Mock
110     private lateinit var goneToLockscreenTransitionViewModel: GoneToLockscreenTransitionViewModel
111     @Mock
112     private lateinit var occludedToLockscreenTransitionViewModel:
113         OccludedToLockscreenTransitionViewModel
114     @Mock
115     private lateinit var offToLockscreenTransitionViewModel: OffToLockscreenTransitionViewModel
116     @Mock
117     private lateinit var primaryBouncerToLockscreenTransitionViewModel:
118         PrimaryBouncerToLockscreenTransitionViewModel
119     @Mock
120     private lateinit var lockscreenToAodTransitionViewModel: LockscreenToAodTransitionViewModel
121     @Mock
122     private lateinit var lockscreenToDozingTransitionViewModel:
123         LockscreenToDozingTransitionViewModel
124     @Mock
125     private lateinit var lockscreenToDreamingHostedTransitionViewModel:
126         LockscreenToDreamingHostedTransitionViewModel
127     @Mock
128     private lateinit var lockscreenToDreamingTransitionViewModel:
129         LockscreenToDreamingTransitionViewModel
130     @Mock
131     private lateinit var lockscreenToGoneTransitionViewModel: LockscreenToGoneTransitionViewModel
132     @Mock
133     private lateinit var lockscreenToOccludedTransitionViewModel:
134         LockscreenToOccludedTransitionViewModel
135     @Mock
136     private lateinit var lockscreenToPrimaryBouncerTransitionViewModel:
137         LockscreenToPrimaryBouncerTransitionViewModel
138     @Mock
139     private lateinit var lockscreenToGlanceableHubTransitionViewModel:
140         LockscreenToGlanceableHubTransitionViewModel
141     @Mock
142     private lateinit var glanceableHubToLockscreenTransitionViewModel:
143         GlanceableHubToLockscreenTransitionViewModel
144     @Mock private lateinit var transitionInteractor: KeyguardTransitionInteractor
145 
146     private val kosmos = testKosmos()
147 
148     private lateinit var underTest: KeyguardQuickAffordancesCombinedViewModel
149 
150     private val testScope = kosmos.testScope
151     private lateinit var repository: FakeKeyguardRepository
152     private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
153     private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
154     private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
155     private lateinit var dockManager: DockManagerFake
156     private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
157     private lateinit var keyguardInteractor: KeyguardInteractor
158 
159     private val intendedAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(1f)
160     // the viewModel does a `map { 1 - it }` on this value, which is why it's different
161     private val intendedShadeAlphaMutableStateFlow: MutableStateFlow<Float> = MutableStateFlow(0f)
162 
163     private val intendedFinishedKeyguardStateFlow = MutableStateFlow(KeyguardState.LOCKSCREEN)
164 
165     @Before
setUpnull166     fun setUp() {
167         MockitoAnnotations.initMocks(this)
168 
169         overrideResource(R.bool.custom_lockscreen_shortcuts_enabled, true)
170         overrideResource(
171             R.array.config_keyguardQuickAffordanceDefaults,
172             arrayOf(
173                 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
174                     ":" +
175                     BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
176                 KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
177                     ":" +
178                     BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
179             )
180         )
181 
182         homeControlsQuickAffordanceConfig =
183             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
184         quickAccessWalletAffordanceConfig =
185             FakeKeyguardQuickAffordanceConfig(
186                 BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
187             )
188         qrCodeScannerAffordanceConfig =
189             FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
190         dockManager = DockManagerFake()
191         biometricSettingsRepository = FakeBiometricSettingsRepository()
192 
193         mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
194 
195         val featureFlags =
196             FakeFeatureFlags().apply {
197                 set(Flags.LOCK_SCREEN_LONG_PRESS_ENABLED, false)
198                 set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, false)
199             }
200 
201         val withDeps = KeyguardInteractorFactory.create(featureFlags = featureFlags)
202         keyguardInteractor = withDeps.keyguardInteractor
203         repository = withDeps.repository
204 
205         whenever(userTracker.userHandle).thenReturn(mock())
206         whenever(lockPatternUtils.getStrongAuthForUser(ArgumentMatchers.anyInt()))
207             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
208 
209         val localUserSelectionManager =
210             KeyguardQuickAffordanceLocalUserSelectionManager(
211                 context = context,
212                 userFileManager =
213                     mock<UserFileManager>().apply {
214                         whenever(
215                                 getSharedPreferences(
216                                     ArgumentMatchers.anyString(),
217                                     ArgumentMatchers.anyInt(),
218                                     ArgumentMatchers.anyInt(),
219                                 )
220                             )
221                             .thenReturn(FakeSharedPreferences())
222                     },
223                 userTracker = userTracker,
224                 systemSettings = FakeSettings(),
225                 broadcastDispatcher = fakeBroadcastDispatcher,
226             )
227         val remoteUserSelectionManager =
228             KeyguardQuickAffordanceRemoteUserSelectionManager(
229                 scope = testScope.backgroundScope,
230                 userTracker = userTracker,
231                 clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
232                 userHandle = UserHandle.SYSTEM,
233             )
234         val quickAffordanceRepository =
235             KeyguardQuickAffordanceRepository(
236                 appContext = context,
237                 scope = testScope.backgroundScope,
238                 localUserSelectionManager = localUserSelectionManager,
239                 remoteUserSelectionManager = remoteUserSelectionManager,
240                 userTracker = userTracker,
241                 legacySettingSyncer =
242                     KeyguardQuickAffordanceLegacySettingSyncer(
243                         scope = testScope.backgroundScope,
244                         backgroundDispatcher = kosmos.testDispatcher,
245                         secureSettings = FakeSettings(),
246                         selectionsManager = localUserSelectionManager,
247                     ),
248                 configs =
249                     setOf(
250                         homeControlsQuickAffordanceConfig,
251                         quickAccessWalletAffordanceConfig,
252                         qrCodeScannerAffordanceConfig,
253                     ),
254                 dumpManager = mock(),
255                 userHandle = UserHandle.SYSTEM,
256             )
257 
258         intendedAlphaMutableStateFlow.value = 1f
259         intendedShadeAlphaMutableStateFlow.value = 0f
260         intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
261         whenever(aodToLockscreenTransitionViewModel.shortcutsAlpha)
262             .thenReturn(intendedAlphaMutableStateFlow)
263         whenever(dozingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
264         whenever(dreamingHostedToLockscreenTransitionViewModel.shortcutsAlpha)
265             .thenReturn(emptyFlow())
266         whenever(dreamingToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
267         whenever(goneToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
268         whenever(occludedToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
269         whenever(offToLockscreenTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
270         whenever(primaryBouncerToLockscreenTransitionViewModel.shortcutsAlpha)
271             .thenReturn(emptyFlow())
272         whenever(lockscreenToAodTransitionViewModel.shortcutsAlpha)
273             .thenReturn(intendedAlphaMutableStateFlow)
274         whenever(lockscreenToDozingTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
275         whenever(lockscreenToDreamingHostedTransitionViewModel.shortcutsAlpha)
276             .thenReturn(emptyFlow())
277         whenever(lockscreenToDreamingTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
278         whenever(lockscreenToGoneTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
279         whenever(lockscreenToOccludedTransitionViewModel.shortcutsAlpha).thenReturn(emptyFlow())
280         whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha)
281             .thenReturn(emptyFlow())
282         whenever(lockscreenToGlanceableHubTransitionViewModel.shortcutsAlpha)
283             .thenReturn(emptyFlow())
284         whenever(glanceableHubToLockscreenTransitionViewModel.shortcutsAlpha)
285             .thenReturn(emptyFlow())
286         whenever(shadeInteractor.anyExpansion).thenReturn(intendedShadeAlphaMutableStateFlow)
287         whenever(transitionInteractor.finishedKeyguardState)
288             .thenReturn(intendedFinishedKeyguardStateFlow)
289 
290         underTest =
291             KeyguardQuickAffordancesCombinedViewModel(
292                 quickAffordanceInteractor =
293                     KeyguardQuickAffordanceInteractor(
294                         keyguardInteractor = keyguardInteractor,
295                         shadeInteractor = shadeInteractor,
296                         lockPatternUtils = lockPatternUtils,
297                         keyguardStateController = keyguardStateController,
298                         userTracker = userTracker,
299                         activityStarter = activityStarter,
300                         featureFlags = featureFlags,
301                         repository = { quickAffordanceRepository },
302                         launchAnimator = launchAnimator,
303                         logger = logger,
304                         devicePolicyManager = devicePolicyManager,
305                         dockManager = dockManager,
306                         biometricSettingsRepository = biometricSettingsRepository,
307                         backgroundDispatcher = kosmos.testDispatcher,
308                         appContext = mContext,
309                         sceneInteractor = { kosmos.sceneInteractor },
310                     ),
311                 keyguardInteractor = keyguardInteractor,
312                 shadeInteractor = shadeInteractor,
313                 aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
314                 dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
315                 dreamingHostedToLockscreenTransitionViewModel =
316                     dreamingHostedToLockscreenTransitionViewModel,
317                 dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
318                 goneToLockscreenTransitionViewModel = goneToLockscreenTransitionViewModel,
319                 occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
320                 offToLockscreenTransitionViewModel = offToLockscreenTransitionViewModel,
321                 primaryBouncerToLockscreenTransitionViewModel =
322                     primaryBouncerToLockscreenTransitionViewModel,
323                 glanceableHubToLockscreenTransitionViewModel =
324                     glanceableHubToLockscreenTransitionViewModel,
325                 lockscreenToAodTransitionViewModel = lockscreenToAodTransitionViewModel,
326                 lockscreenToDozingTransitionViewModel = lockscreenToDozingTransitionViewModel,
327                 lockscreenToDreamingHostedTransitionViewModel =
328                     lockscreenToDreamingHostedTransitionViewModel,
329                 lockscreenToDreamingTransitionViewModel = lockscreenToDreamingTransitionViewModel,
330                 lockscreenToGoneTransitionViewModel = lockscreenToGoneTransitionViewModel,
331                 lockscreenToOccludedTransitionViewModel = lockscreenToOccludedTransitionViewModel,
332                 lockscreenToPrimaryBouncerTransitionViewModel =
333                     lockscreenToPrimaryBouncerTransitionViewModel,
334                 lockscreenToGlanceableHubTransitionViewModel =
335                     lockscreenToGlanceableHubTransitionViewModel,
336                 transitionInteractor = transitionInteractor,
337             )
338     }
339 
340     @Test
startButton_present_visibleModel_startsActivityOnClicknull341     fun startButton_present_visibleModel_startsActivityOnClick() =
342         testScope.runTest {
343             repository.setKeyguardShowing(true)
344             val latest = collectLastValue(underTest.startButton)
345 
346             val testConfig =
347                 TestConfig(
348                     isVisible = true,
349                     isClickable = true,
350                     isActivated = true,
351                     icon = mock(),
352                     canShowWhileLocked = false,
353                     intent = Intent("action"),
354                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
355                 )
356             val configKey =
357                 setUpQuickAffordanceModel(
358                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
359                     testConfig = testConfig,
360                 )
361 
362             assertQuickAffordanceViewModel(
363                 viewModel = latest(),
364                 testConfig = testConfig,
365                 configKey = configKey,
366             )
367         }
368 
369     @Test
startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeaturesnull370     fun startButton_hiddenWhenDevicePolicyDisablesAllKeyguardFeatures() =
371         testScope.runTest {
372             whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId))
373                 .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_ALL)
374             repository.setKeyguardShowing(true)
375             val latest by collectLastValue(underTest.startButton)
376 
377             val testConfig =
378                 TestConfig(
379                     isVisible = true,
380                     isClickable = true,
381                     isActivated = true,
382                     icon = mock(),
383                     canShowWhileLocked = false,
384                     intent = Intent("action"),
385                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
386                 )
387             val configKey =
388                 setUpQuickAffordanceModel(
389                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
390                     testConfig = testConfig,
391                 )
392 
393             assertQuickAffordanceViewModel(
394                 viewModel = latest,
395                 testConfig =
396                     TestConfig(
397                         isVisible = false,
398                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
399                     ),
400                 configKey = configKey,
401             )
402         }
403 
404     @Test
startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowingnull405     fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
406         testScope.runTest {
407             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
408             underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
409 
410             repository.setKeyguardShowing(false)
411             val latest = collectLastValue(underTest.startButton)
412 
413             val icon: Icon = mock()
414             val configKey =
415                 setUpQuickAffordanceModel(
416                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
417                     testConfig =
418                         TestConfig(
419                             isVisible = true,
420                             isClickable = true,
421                             isActivated = true,
422                             icon = icon,
423                             canShowWhileLocked = false,
424                             intent = Intent("action"),
425                             slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
426                         ),
427                 )
428 
429             assertQuickAffordanceViewModel(
430                 viewModel = latest(),
431                 testConfig =
432                     TestConfig(
433                         isVisible = true,
434                         isClickable = false,
435                         isActivated = false,
436                         icon = icon,
437                         canShowWhileLocked = false,
438                         intent = Intent("action"),
439                         isSelected = true,
440                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
441                     ),
442                 configKey = configKey,
443             )
444             Truth.assertThat(latest()?.isSelected).isTrue()
445         }
446 
447     @Test
endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelectednull448     fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
449         testScope.runTest {
450             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
451             underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
452 
453             repository.setKeyguardShowing(false)
454             val endButton = collectLastValue(underTest.endButton)
455 
456             val icon: Icon = mock()
457             setUpQuickAffordanceModel(
458                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
459                 testConfig =
460                     TestConfig(
461                         isVisible = true,
462                         isClickable = true,
463                         isActivated = true,
464                         icon = icon,
465                         canShowWhileLocked = false,
466                         intent = Intent("action"),
467                         slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
468                     ),
469             )
470             val configKey =
471                 setUpQuickAffordanceModel(
472                     position = KeyguardQuickAffordancePosition.BOTTOM_END,
473                     testConfig =
474                         TestConfig(
475                             isVisible = true,
476                             isClickable = true,
477                             isActivated = true,
478                             icon = icon,
479                             canShowWhileLocked = false,
480                             intent = Intent("action"),
481                             slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
482                         ),
483                 )
484 
485             assertQuickAffordanceViewModel(
486                 viewModel = endButton(),
487                 testConfig =
488                     TestConfig(
489                         isVisible = true,
490                         isClickable = false,
491                         isActivated = false,
492                         icon = icon,
493                         canShowWhileLocked = false,
494                         intent = Intent("action"),
495                         isDimmed = true,
496                         slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
497                     ),
498                 configKey = configKey,
499             )
500         }
501 
502     @Test
endButton_present_visibleModel_doNothingOnClicknull503     fun endButton_present_visibleModel_doNothingOnClick() =
504         testScope.runTest {
505             repository.setKeyguardShowing(true)
506             val latest = collectLastValue(underTest.endButton)
507 
508             val config =
509                 TestConfig(
510                     isVisible = true,
511                     isClickable = true,
512                     icon = mock(),
513                     canShowWhileLocked = false,
514                     intent =
515                         null, // This will cause it to tell the system that the click was handled.
516                     slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId(),
517                 )
518             val configKey =
519                 setUpQuickAffordanceModel(
520                     position = KeyguardQuickAffordancePosition.BOTTOM_END,
521                     testConfig = config,
522                 )
523 
524             assertQuickAffordanceViewModel(
525                 viewModel = latest(),
526                 testConfig = config,
527                 configKey = configKey,
528             )
529         }
530 
531     @Test
startButton_notPresent_modelIsHiddennull532     fun startButton_notPresent_modelIsHidden() =
533         testScope.runTest {
534             val latest = collectLastValue(underTest.startButton)
535 
536             val config =
537                 TestConfig(
538                     isVisible = false,
539                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
540                 )
541             val configKey =
542                 setUpQuickAffordanceModel(
543                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
544                     testConfig = config,
545                 )
546 
547             assertQuickAffordanceViewModel(
548                 viewModel = latest(),
549                 testConfig = config,
550                 configKey = configKey,
551             )
552         }
553 
554     @Test
animateButtonRevealnull555     fun animateButtonReveal() =
556         testScope.runTest {
557             repository.setKeyguardShowing(true)
558             val testConfig =
559                 TestConfig(
560                     isVisible = true,
561                     isClickable = true,
562                     icon = mock(),
563                     canShowWhileLocked = false,
564                     intent = Intent("action"),
565                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
566                 )
567 
568             setUpQuickAffordanceModel(
569                 position = KeyguardQuickAffordancePosition.BOTTOM_START,
570                 testConfig = testConfig,
571             )
572 
573             val value = collectLastValue(underTest.startButton.map { it.animateReveal })
574 
575             Truth.assertThat(value()).isFalse()
576             repository.setAnimateDozingTransitions(true)
577             Truth.assertThat(value()).isTrue()
578             repository.setAnimateDozingTransitions(false)
579             Truth.assertThat(value()).isFalse()
580         }
581 
582     @Test
isClickable_trueWhenAlphaAtThresholdnull583     fun isClickable_trueWhenAlphaAtThreshold() =
584         testScope.runTest {
585             repository.setKeyguardShowing(true)
586             repository.setKeyguardAlpha(
587                 KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
588             )
589 
590             val testConfig =
591                 TestConfig(
592                     isVisible = true,
593                     isClickable = true,
594                     icon = mock(),
595                     canShowWhileLocked = false,
596                     intent = Intent("action"),
597                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
598                 )
599             val configKey =
600                 setUpQuickAffordanceModel(
601                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
602                     testConfig = testConfig,
603                 )
604 
605             val latest = collectLastValue(underTest.startButton)
606 
607             assertQuickAffordanceViewModel(
608                 viewModel = latest(),
609                 testConfig = testConfig,
610                 configKey = configKey,
611             )
612         }
613 
614     @Test
isClickable_trueWhenAlphaAboveThresholdnull615     fun isClickable_trueWhenAlphaAboveThreshold() =
616         testScope.runTest {
617             repository.setKeyguardShowing(true)
618             val latest = collectLastValue(underTest.startButton)
619             repository.setKeyguardAlpha(
620                 min(
621                     1f,
622                     KeyguardQuickAffordancesCombinedViewModel
623                         .AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f
624                 ),
625             )
626 
627             val testConfig =
628                 TestConfig(
629                     isVisible = true,
630                     isClickable = true,
631                     icon = mock(),
632                     canShowWhileLocked = false,
633                     intent = Intent("action"),
634                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
635                 )
636             val configKey =
637                 setUpQuickAffordanceModel(
638                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
639                     testConfig = testConfig,
640                 )
641 
642             assertQuickAffordanceViewModel(
643                 viewModel = latest(),
644                 testConfig = testConfig,
645                 configKey = configKey,
646             )
647         }
648 
649     @Test
isClickable_falseWhenAlphaBelowThresholdnull650     fun isClickable_falseWhenAlphaBelowThreshold() =
651         testScope.runTest {
652             intendedAlphaMutableStateFlow.value =
653                 KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD -
654                     .1f
655             // the viewModel does a `map { 1 - it }` on this value, which is why it's different
656             intendedShadeAlphaMutableStateFlow.value =
657                 KeyguardQuickAffordancesCombinedViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD +
658                     .1f
659             repository.setKeyguardShowing(true)
660             val latest = collectLastValue(underTest.startButton)
661 
662             val testConfig =
663                 TestConfig(
664                     isVisible = false,
665                     isClickable = false,
666                     icon = mock(),
667                     canShowWhileLocked = false,
668                     intent = Intent("action"),
669                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
670                 )
671             val configKey =
672                 setUpQuickAffordanceModel(
673                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
674                     testConfig = testConfig,
675                 )
676 
677             assertQuickAffordanceViewModel(
678                 viewModel = latest(),
679                 testConfig = testConfig,
680                 configKey = configKey,
681             )
682         }
683 
684     @Test
isClickable_falseWhenAlphaAtZeronull685     fun isClickable_falseWhenAlphaAtZero() =
686         testScope.runTest {
687             intendedAlphaMutableStateFlow.value = 0f
688             intendedShadeAlphaMutableStateFlow.value = 1f
689             repository.setKeyguardShowing(true)
690             val latest = collectLastValue(underTest.startButton)
691 
692             val testConfig =
693                 TestConfig(
694                     isVisible = false,
695                     isClickable = false,
696                     icon = mock(),
697                     canShowWhileLocked = false,
698                     intent = Intent("action"),
699                     slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
700                 )
701             val configKey =
702                 setUpQuickAffordanceModel(
703                     position = KeyguardQuickAffordancePosition.BOTTOM_START,
704                     testConfig = testConfig,
705                 )
706 
707             assertQuickAffordanceViewModel(
708                 viewModel = latest(),
709                 testConfig = testConfig,
710                 configKey = configKey,
711             )
712         }
713 
714     @Test
shadeExpansionAlpha_changes_whenOnLockscreennull715     fun shadeExpansionAlpha_changes_whenOnLockscreen() =
716         testScope.runTest {
717             intendedFinishedKeyguardStateFlow.value = KeyguardState.LOCKSCREEN
718             intendedShadeAlphaMutableStateFlow.value = 0.25f
719             val underTest = collectLastValue(underTest.transitionAlpha)
720             assertEquals(0.75f, underTest())
721 
722             intendedShadeAlphaMutableStateFlow.value = 0.3f
723             assertEquals(0.7f, underTest())
724         }
725 
726     @Test
shadeExpansionAlpha_alwaysZero_whenNotOnLockscreennull727     fun shadeExpansionAlpha_alwaysZero_whenNotOnLockscreen() =
728         testScope.runTest {
729             intendedFinishedKeyguardStateFlow.value = KeyguardState.GONE
730             intendedShadeAlphaMutableStateFlow.value = 0.5f
731             val underTest = collectLastValue(underTest.transitionAlpha)
732             assertEquals(0f, underTest())
733 
734             intendedShadeAlphaMutableStateFlow.value = 0.25f
735             assertEquals(0f, underTest())
736         }
737 
setUpQuickAffordanceModelnull738     private suspend fun setUpQuickAffordanceModel(
739         position: KeyguardQuickAffordancePosition,
740         testConfig: TestConfig,
741     ): String {
742         val config =
743             when (position) {
744                 KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
745                 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
746             }
747 
748         val lockScreenState =
749             if (testConfig.isVisible) {
750                 if (testConfig.intent != null) {
751                     config.onTriggeredResult =
752                         KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
753                             intent = testConfig.intent,
754                             canShowWhileLocked = testConfig.canShowWhileLocked,
755                         )
756                 }
757                 KeyguardQuickAffordanceConfig.LockScreenState.Visible(
758                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
759                     activationState =
760                         when (testConfig.isActivated) {
761                             true -> ActivationState.Active
762                             false -> ActivationState.Inactive
763                         }
764                 )
765             } else {
766                 KeyguardQuickAffordanceConfig.LockScreenState.Hidden
767             }
768         config.setState(lockScreenState)
769         return "${position.toSlotId()}::${config.key}"
770     }
771 
assertQuickAffordanceViewModelnull772     private fun assertQuickAffordanceViewModel(
773         viewModel: KeyguardQuickAffordanceViewModel?,
774         testConfig: TestConfig,
775         configKey: String,
776     ) {
777         checkNotNull(viewModel)
778         Truth.assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
779         Truth.assertThat(viewModel.isClickable).isEqualTo(testConfig.isClickable)
780         Truth.assertThat(viewModel.isActivated).isEqualTo(testConfig.isActivated)
781         Truth.assertThat(viewModel.isSelected).isEqualTo(testConfig.isSelected)
782         Truth.assertThat(viewModel.isDimmed).isEqualTo(testConfig.isDimmed)
783         Truth.assertThat(viewModel.slotId).isEqualTo(testConfig.slotId)
784         if (testConfig.isVisible) {
785             Truth.assertThat(viewModel.icon).isEqualTo(testConfig.icon)
786             viewModel.onClicked.invoke(
787                 KeyguardQuickAffordanceViewModel.OnClickedParameters(
788                     configKey = configKey,
789                     expandable = expandable,
790                     slotId = viewModel.slotId,
791                 )
792             )
793             if (testConfig.intent != null) {
794                 Truth.assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
795             } else {
796                 Mockito.verifyZeroInteractions(activityStarter)
797             }
798         } else {
799             Truth.assertThat(viewModel.isVisible).isFalse()
800         }
801     }
802 
803     private data class TestConfig(
804         val isVisible: Boolean,
805         val isClickable: Boolean = false,
806         val isActivated: Boolean = false,
807         val icon: Icon? = null,
808         val canShowWhileLocked: Boolean = false,
809         val intent: Intent? = null,
810         val isSelected: Boolean = false,
811         val isDimmed: Boolean = false,
812         val slotId: String = ""
813     ) {
814         init {
<lambda>null815             check(!isVisible || icon != null) { "Must supply non-null icon if visible!" }
816         }
817     }
818 }
819