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