1 package com.android.systemui.statusbar.notification
2 
3 import android.view.View
4 import androidx.test.filters.SmallTest
5 import com.android.systemui.SysuiTestCase
6 import com.android.systemui.flags.FakeFeatureFlags
7 import com.android.systemui.flags.FeatureFlags
8 import com.android.systemui.flags.Flags
9 import com.android.systemui.util.mockito.mock
10 import com.android.systemui.util.mockito.whenever
11 import org.junit.Assert.assertEquals
12 import org.junit.Test
13 import org.junit.runner.RunWith
14 import org.junit.runners.JUnit4
15 import org.mockito.Mockito.atLeastOnce
16 import org.mockito.Mockito.times
17 import org.mockito.Mockito.verify
18 
19 @SmallTest
20 @RunWith(JUnit4::class)
21 class RoundableTest : SysuiTestCase() {
22     private val targetView: View = mock()
23     private val featureFlags = FakeFeatureFlags()
24     private val roundable = FakeRoundable(targetView = targetView, featureFlags = featureFlags)
25 
26     @Test
27     fun defaultConfig_shouldNotHaveRoundedCorner() {
28         // the expected default value for the roundness is top = 0f, bottom = 0f
29         assertEquals(0f, roundable.roundableState.topRoundness)
30         assertEquals(0f, roundable.roundableState.bottomRoundness)
31         assertEquals(false, roundable.hasRoundedCorner())
32     }
33 
34     @Test
35     fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
36         roundable.applyRoundnessAndInvalidate()
37 
38         verify(targetView, times(1)).invalidate()
39     }
40 
41     @Test
42     fun requestTopRoundness_update_and_invalidate_targetView() {
43         roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
44 
45         assertEquals(1f, roundable.roundableState.topRoundness)
46         verify(targetView, times(1)).invalidate()
47     }
48 
49     @Test
50     fun requestBottomRoundness_update_and_invalidate_targetView() {
51         roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
52 
53         assertEquals(1f, roundable.roundableState.bottomRoundness)
54         verify(targetView, times(1)).invalidate()
55     }
56 
57     @Test
58     fun requestRoundness_update_and_invalidate_targetView() {
59         roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
60 
61         assertEquals(1f, roundable.roundableState.topRoundness)
62         assertEquals(1f, roundable.roundableState.bottomRoundness)
63         verify(targetView, atLeastOnce()).invalidate()
64     }
65 
66     @Test
67     fun requestRoundnessReset_update_and_invalidate_targetView() {
68         roundable.requestRoundness(1f, 1f, SOURCE1)
69         assertEquals(1f, roundable.roundableState.topRoundness)
70         assertEquals(1f, roundable.roundableState.bottomRoundness)
71 
72         roundable.requestRoundnessReset(sourceType = SOURCE1)
73 
74         assertEquals(0f, roundable.roundableState.topRoundness)
75         assertEquals(0f, roundable.roundableState.bottomRoundness)
76         verify(targetView, atLeastOnce()).invalidate()
77     }
78 
79     @Test
80     fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
81         roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
82         assertEquals(true, roundable.hasRoundedCorner())
83 
84         roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
85         assertEquals(true, roundable.hasRoundedCorner())
86 
87         roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
88         assertEquals(true, roundable.hasRoundedCorner())
89 
90         roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
91         assertEquals(false, roundable.hasRoundedCorner())
92     }
93 
94     @Test
95     fun roundness_take_maxValue_onMultipleSources_first_lower() {
96         roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
97         assertEquals(0.1f, roundable.roundableState.topRoundness)
98         assertEquals(0.1f, roundable.roundableState.bottomRoundness)
99 
100         roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
101         // SOURCE1 has 0.1f - SOURCE2 has 0.2f
102         assertEquals(0.2f, roundable.roundableState.topRoundness)
103         assertEquals(0.2f, roundable.roundableState.bottomRoundness)
104     }
105 
106     @Test
107     fun roundness_take_maxValue_onMultipleSources_first_higher() {
108         roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
109         assertEquals(0.5f, roundable.roundableState.topRoundness)
110         assertEquals(0.5f, roundable.roundableState.bottomRoundness)
111 
112         roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
113         // SOURCE1 has 0.5f - SOURCE2 has 0.1f
114         assertEquals(0.5f, roundable.roundableState.topRoundness)
115         assertEquals(0.5f, roundable.roundableState.bottomRoundness)
116     }
117 
118     @Test
119     fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
120         roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
121         assertEquals(0.1f, roundable.roundableState.topRoundness)
122         assertEquals(0.1f, roundable.roundableState.bottomRoundness)
123 
124         roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
125         // SOURCE1 has 0.1f - SOURCE2 has 0.2f
126         assertEquals(0.2f, roundable.roundableState.topRoundness)
127         assertEquals(0.2f, roundable.roundableState.bottomRoundness)
128 
129         roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
130         // SOURCE1 has 0.3f - SOURCE2 has 0.2f
131         assertEquals(0.3f, roundable.roundableState.topRoundness)
132         assertEquals(0.3f, roundable.roundableState.bottomRoundness)
133     }
134 
135     @Test
136     fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
137         roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
138         assertEquals(0.5f, roundable.roundableState.topRoundness)
139         assertEquals(0.5f, roundable.roundableState.bottomRoundness)
140 
141         roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
142         // SOURCE1 has 0.5f - SOURCE2 has 0.2f
143         assertEquals(0.5f, roundable.roundableState.topRoundness)
144         assertEquals(0.5f, roundable.roundableState.bottomRoundness)
145 
146         roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
147         // SOURCE1 has 0.1f - SOURCE2 has 0.2f
148         assertEquals(0.2f, roundable.roundableState.topRoundness)
149         assertEquals(0.2f, roundable.roundableState.bottomRoundness)
150     }
151 
152     @Test
153     fun getCornerRadii_radius_maxed_to_height() {
154         whenever(targetView.height).thenReturn(10)
155         featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
156         roundable.requestRoundness(1f, 1f, SOURCE1)
157 
158         assertCornerRadiiEquals(5f, 5f)
159     }
160 
161     @Test
162     fun getCornerRadii_topRadius_maxed_to_height() {
163         whenever(targetView.height).thenReturn(5)
164         featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
165         roundable.requestRoundness(1f, 0f, SOURCE1)
166 
167         assertCornerRadiiEquals(5f, 0f)
168     }
169 
170     @Test
171     fun getCornerRadii_bottomRadius_maxed_to_height() {
172         whenever(targetView.height).thenReturn(5)
173         featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
174         roundable.requestRoundness(0f, 1f, SOURCE1)
175 
176         assertCornerRadiiEquals(0f, 5f)
177     }
178 
179     @Test
180     fun getCornerRadii_radii_kept() {
181         whenever(targetView.height).thenReturn(100)
182         featureFlags.set(Flags.IMPROVED_HUN_ANIMATIONS, true)
183         roundable.requestRoundness(1f, 1f, SOURCE1)
184 
185         assertCornerRadiiEquals(MAX_RADIUS, MAX_RADIUS)
186     }
187 
188     private fun assertCornerRadiiEquals(top: Float, bottom: Float) {
189         assertEquals("topCornerRadius", top, roundable.topCornerRadius)
190         assertEquals("bottomCornerRadius", bottom, roundable.bottomCornerRadius)
191     }
192 
193     class FakeRoundable(
194         targetView: View,
195         radius: Float = MAX_RADIUS,
196         featureFlags: FeatureFlags
197     ) : Roundable {
198         override val roundableState =
199             RoundableState(
200                 targetView = targetView,
201                 roundable = this,
202                 maxRadius = radius,
203                 featureFlags = featureFlags
204             )
205 
206         override val clipHeight: Int
207             get() = roundableState.targetView.height
208     }
209 
210     companion object {
211         private const val MAX_RADIUS = 10f
212         private val SOURCE1 = SourceType.from("Source1")
213         private val SOURCE2 = SourceType.from("Source2")
214     }
215 }
216