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 package com.android.systemui.keyguard.ui.viewmodel
18 
19 import androidx.test.filters.SmallTest
20 import com.android.systemui.SysuiTestCase
21 import com.android.systemui.coroutines.collectLastValue
22 import com.android.systemui.doze.util.BurnInHelperWrapper
23 import com.android.systemui.flags.FeatureFlags
24 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
25 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
26 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
27 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
28 import com.android.systemui.util.mockito.any
29 import com.android.systemui.util.mockito.mock
30 import com.android.systemui.util.mockito.whenever
31 import com.google.common.truth.Truth.assertThat
32 import kotlinx.coroutines.ExperimentalCoroutinesApi
33 import kotlinx.coroutines.flow.MutableStateFlow
34 import kotlinx.coroutines.test.runTest
35 import org.junit.Before
36 import org.junit.Test
37 import org.junit.runner.RunWith
38 import org.junit.runners.JUnit4
39 import org.mockito.ArgumentMatchers.anyInt
40 import org.mockito.Mock
41 import org.mockito.MockitoAnnotations
42 
43 @OptIn(ExperimentalCoroutinesApi::class)
44 @SmallTest
45 @RunWith(JUnit4::class)
46 class KeyguardIndicationAreaViewModelTest : SysuiTestCase() {
47 
48     @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
49     @Mock private lateinit var shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel
50     @Mock private lateinit var featureFlags: FeatureFlags
51 
52     private lateinit var underTest: KeyguardIndicationAreaViewModel
53     private lateinit var repository: FakeKeyguardRepository
54 
55     private val startButtonFlow =
56         MutableStateFlow<KeyguardQuickAffordanceViewModel>(
57             KeyguardQuickAffordanceViewModel(
58                 slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
59             )
60         )
61     private val endButtonFlow =
62         MutableStateFlow<KeyguardQuickAffordanceViewModel>(
63             KeyguardQuickAffordanceViewModel(
64                 slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
65             )
66         )
67     private val alphaFlow = MutableStateFlow<Float>(1f)
68 
69     @Before
70     fun setUp() {
71         MockitoAnnotations.initMocks(this)
72         whenever(burnInHelperWrapper.burnInOffset(anyInt(), any()))
73             .thenReturn(RETURNED_BURN_IN_OFFSET)
74 
75         val withDeps = KeyguardInteractorFactory.create()
76         val keyguardInteractor = withDeps.keyguardInteractor
77         repository = withDeps.repository
78 
79         val bottomAreaViewModel: KeyguardBottomAreaViewModel = mock()
80         whenever(bottomAreaViewModel.startButton).thenReturn(startButtonFlow)
81         whenever(bottomAreaViewModel.endButton).thenReturn(endButtonFlow)
82         whenever(bottomAreaViewModel.alpha).thenReturn(alphaFlow)
83         underTest =
84             KeyguardIndicationAreaViewModel(
85                 keyguardInteractor = keyguardInteractor,
86                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
87                 keyguardBottomAreaViewModel = bottomAreaViewModel,
88                 burnInHelperWrapper = burnInHelperWrapper,
89                 shortcutsCombinedViewModel = shortcutsCombinedViewModel,
90                 featureFlags = featureFlags,
91             )
92     }
93 
94     @Test
95     fun alpha() = runTest {
96         val value = collectLastValue(underTest.alpha)
97 
98         assertThat(value()).isEqualTo(1f)
99         alphaFlow.value = 0.1f
100         assertThat(value()).isEqualTo(0.1f)
101         alphaFlow.value = 0.5f
102         assertThat(value()).isEqualTo(0.5f)
103         alphaFlow.value = 0.2f
104         assertThat(value()).isEqualTo(0.2f)
105         alphaFlow.value = 0f
106         assertThat(value()).isEqualTo(0f)
107     }
108 
109     @Test
110     fun isIndicationAreaPadded() = runTest {
111         repository.setKeyguardShowing(true)
112         val value = collectLastValue(underTest.isIndicationAreaPadded)
113 
114         assertThat(value()).isFalse()
115         startButtonFlow.value = startButtonFlow.value.copy(isVisible = true)
116         assertThat(value()).isTrue()
117         endButtonFlow.value = endButtonFlow.value.copy(isVisible = true)
118         assertThat(value()).isTrue()
119         startButtonFlow.value = startButtonFlow.value.copy(isVisible = false)
120         assertThat(value()).isTrue()
121         endButtonFlow.value = endButtonFlow.value.copy(isVisible = false)
122         assertThat(value()).isFalse()
123     }
124 
125     @Test
126     fun indicationAreaTranslationX() = runTest {
127         val value = collectLastValue(underTest.indicationAreaTranslationX)
128 
129         assertThat(value()).isEqualTo(0f)
130         repository.setClockPosition(100, 100)
131         assertThat(value()).isEqualTo(100f)
132         repository.setClockPosition(200, 100)
133         assertThat(value()).isEqualTo(200f)
134         repository.setClockPosition(200, 200)
135         assertThat(value()).isEqualTo(200f)
136         repository.setClockPosition(300, 100)
137         assertThat(value()).isEqualTo(300f)
138     }
139 
140     @Test
141     fun indicationAreaTranslationY() = runTest {
142         val value = collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
143 
144         // Negative 0 - apparently there's a difference in floating point arithmetic - FML
145         assertThat(value()).isEqualTo(-0f)
146         val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
147         assertThat(value()).isEqualTo(expected1)
148         val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
149         assertThat(value()).isEqualTo(expected2)
150         val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
151         assertThat(value()).isEqualTo(expected3)
152         val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
153         assertThat(value()).isEqualTo(expected4)
154     }
155 
156     private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
157         repository.setDozeAmount(dozeAmount)
158         return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
159     }
160 
161     companion object {
162         private const val DEFAULT_BURN_IN_OFFSET = 5
163         private const val RETURNED_BURN_IN_OFFSET = 3
164     }
165 }
166