1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.systemui.statusbar.phone
17 
18 import android.service.notification.StatusBarNotification
19 import android.testing.AndroidTestingRunner
20 import android.testing.TestableLooper.RunWithLooper
21 import androidx.test.filters.SmallTest
22 import com.android.systemui.SysuiTestCase
23 import com.android.systemui.statusbar.StatusBarIconView
24 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
25 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
26 import junit.framework.Assert.assertEquals
27 import junit.framework.Assert.assertFalse
28 import junit.framework.Assert.assertTrue
29 import org.junit.Test
30 import org.junit.runner.RunWith
31 import org.mockito.Mockito.mock
32 import org.mockito.Mockito.`when` as whenever
33 
34 /**
35  * Tests for {@link NotificationIconContainer}.
36  */
37 @SmallTest
38 @RunWith(AndroidTestingRunner::class)
39 @RunWithLooper
40 class NotificationIconContainerTest : SysuiTestCase() {
41 
42     private val iconContainer = NotificationIconContainer(context, /* attrs= */ null)
43 
44     @Test
45     fun calculateWidthFor_zeroIcons_widthIsZero() {
46         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 0f),
47                 /* actual= */ 0f)
48     }
49 
50     @Test
51     fun calculateWidthFor_oneIcon_widthForOneIcon() {
52         iconContainer.setActualPaddingStart(10f)
53         iconContainer.setActualPaddingEnd(10f)
54         iconContainer.setIconSize(10)
55 
56         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
57                 /* actual= */ 30f)
58     }
59 
60     @Test
61     fun calculateWidthFor_fourIcons_widthForFourIcons() {
62         iconContainer.setActualPaddingStart(10f)
63         iconContainer.setActualPaddingEnd(10f)
64         iconContainer.setIconSize(10)
65 
66         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
67                 /* actual= */ 60f)
68     }
69 
70     @Test
71     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
72         iconContainer.setActualPaddingStart(10f)
73         iconContainer.setActualPaddingEnd(10f)
74         iconContainer.setIconSize(10)
75         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
76                 /* actual= */ 60f)
77     }
78 
79     @Test
80     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
81         iconContainer.setActualPaddingStart(10f)
82         iconContainer.setActualPaddingEnd(10f)
83         iconContainer.setIconSize(10)
84 
85         val icon = mockStatusBarIcon()
86         iconContainer.addView(icon)
87         assertEquals(1, iconContainer.childCount)
88 
89         val iconState = iconContainer.getIconState(icon)
90         iconState.iconAppearAmount = 1f
91 
92         val width = iconContainer.calculateWidthFor(/* numIcons= */ 1f)
93         iconContainer.setActualLayoutWidth(width.toInt())
94 
95         iconContainer.calculateIconXTranslations()
96         assertEquals(10f, iconState.xTranslation)
97         assertFalse(iconContainer.areIconsOverflowing())
98     }
99 
100     @Test
101     fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
102         iconContainer.setActualPaddingStart(10f)
103         iconContainer.setActualPaddingEnd(10f)
104         iconContainer.setIconSize(10)
105 
106         val iconOne = mockStatusBarIcon()
107         val iconTwo = mockStatusBarIcon()
108         val iconThree = mockStatusBarIcon()
109         val iconFour = mockStatusBarIcon()
110 
111         iconContainer.addView(iconOne)
112         iconContainer.addView(iconTwo)
113         iconContainer.addView(iconThree)
114         iconContainer.addView(iconFour)
115         assertEquals(4, iconContainer.childCount)
116 
117         val width = iconContainer.calculateWidthFor(/* numIcons= */ 4f)
118         iconContainer.setActualLayoutWidth(width.toInt())
119 
120         iconContainer.calculateIconXTranslations()
121         assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
122         assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
123         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
124         assertEquals(40f, iconContainer.getIconState(iconFour).xTranslation)
125 
126         assertFalse(iconContainer.areIconsOverflowing())
127     }
128 
129     @Test
130     fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
131         iconContainer.setActualPaddingStart(10f)
132         iconContainer.setActualPaddingEnd(10f)
133         iconContainer.setIconSize(10)
134 
135         val iconOne = mockStatusBarIcon()
136         val iconTwo = mockStatusBarIcon()
137         val iconThree = mockStatusBarIcon()
138         val iconFour = mockStatusBarIcon()
139         val iconFive = mockStatusBarIcon()
140 
141         iconContainer.addView(iconOne)
142         iconContainer.addView(iconTwo)
143         iconContainer.addView(iconThree)
144         iconContainer.addView(iconFour)
145         iconContainer.addView(iconFive)
146         assertEquals(5, iconContainer.childCount)
147 
148         val width = iconContainer.calculateWidthFor(/* numIcons= */ 5f)
149         iconContainer.setActualLayoutWidth(width.toInt())
150 
151         iconContainer.calculateIconXTranslations()
152         assertEquals(10f, iconContainer.getIconState(iconOne).xTranslation)
153         assertEquals(20f, iconContainer.getIconState(iconTwo).xTranslation)
154         assertEquals(30f, iconContainer.getIconState(iconThree).xTranslation)
155         assertTrue(iconContainer.areIconsOverflowing())
156     }
157 
158     @Test
159     fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
160         iconContainer.setActualPaddingStart(0f)
161         iconContainer.setActualPaddingEnd(0f)
162         iconContainer.setActualLayoutWidth(30)
163         iconContainer.setIconSize(10)
164 
165         val iconOne = mockStatusBarIcon()
166         val iconTwo = mockStatusBarIcon()
167         val iconThree = mockStatusBarIcon()
168 
169         iconContainer.addView(iconOne)
170         iconContainer.addView(iconTwo)
171         iconContainer.addView(iconThree)
172         assertEquals(3, iconContainer.childCount)
173 
174         iconContainer.calculateIconXTranslations()
175         assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
176         assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
177         assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
178         assertFalse(iconContainer.areIconsOverflowing())
179     }
180 
181     @Test
182     fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
183         iconContainer.setActualPaddingStart(0f)
184         iconContainer.setActualPaddingEnd(0f)
185         iconContainer.setActualLayoutWidth(35)
186         iconContainer.setIconSize(10)
187 
188         val iconOne = mockStatusBarIcon()
189         val iconTwo = mockStatusBarIcon()
190         val iconThree = mockStatusBarIcon()
191         val iconFour = mockStatusBarIcon()
192 
193         iconContainer.addView(iconOne)
194         iconContainer.addView(iconTwo)
195         iconContainer.addView(iconThree)
196         iconContainer.addView(iconFour)
197         assertEquals(4, iconContainer.childCount)
198 
199         iconContainer.calculateIconXTranslations()
200         assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
201         assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
202         assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
203         assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
204         assertTrue(iconContainer.areIconsOverflowing())
205     }
206 
207     @Test
208     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
209         val forceOverflow = iconContainer.shouldForceOverflow(
210                 /* i= */ 1,
211                 /* speedBumpIndex= */ 0,
212                 /* iconAppearAmount= */ 1f,
213                 /* maxVisibleIcons= */ 5
214         )
215         assertTrue(forceOverflow)
216     }
217 
218     @Test
219     fun shouldForceOverflow_moreThanMaxVisible_true() {
220         val forceOverflow = iconContainer.shouldForceOverflow(
221                 /* i= */ 10,
222                 /* speedBumpIndex= */ 11,
223                 /* iconAppearAmount= */ 0f,
224                 /* maxVisibleIcons= */ 5
225         )
226         assertTrue(forceOverflow)
227     }
228 
229     @Test
230     fun shouldForceOverflow_belowSpeedBumpAndLessThanMaxVisible_false() {
231         val forceOverflow = iconContainer.shouldForceOverflow(
232                 /* i= */ 0,
233                 /* speedBumpIndex= */ 11,
234                 /* iconAppearAmount= */ 0f,
235                 /* maxVisibleIcons= */ 5
236         )
237         assertFalse(forceOverflow)
238     }
239 
240     @Test
241     fun isOverflowing_lastChildXLessThanLayoutEnd_false() {
242         val isOverflowing = iconContainer.isOverflowing(
243                 /* isLastChild= */ true,
244                 /* translationX= */ 0f,
245                 /* layoutEnd= */ 10f,
246                 /* iconSize= */ 2f,
247         )
248         assertFalse(isOverflowing)
249     }
250 
251 
252     @Test
253     fun isOverflowing_lastChildXEqualToLayoutEnd_true() {
254         val isOverflowing = iconContainer.isOverflowing(
255                 /* isLastChild= */ true,
256                 /* translationX= */ 10f,
257                 /* layoutEnd= */ 10f,
258                 /* iconSize= */ 2f,
259         )
260         assertTrue(isOverflowing)
261     }
262 
263     @Test
264     fun isOverflowing_lastChildXGreaterThanDotX_true() {
265         val isOverflowing = iconContainer.isOverflowing(
266                 /* isLastChild= */ true,
267                 /* translationX= */ 9f,
268                 /* layoutEnd= */ 10f,
269                 /* iconSize= */ 2f,
270         )
271         assertTrue(isOverflowing)
272     }
273 
274     @Test
275     fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
276         val isOverflowing = iconContainer.isOverflowing(
277                 /* isLastChild= */ true,
278                 /* translationX= */ 20f,
279                 /* layoutEnd= */ 10f,
280                 /* iconSize= */ 2f,
281         )
282         assertTrue(isOverflowing)
283     }
284 
285     @Test
286     fun isOverflowing_notLastChildXLessThanDotX_false() {
287         val isOverflowing = iconContainer.isOverflowing(
288                 /* isLastChild= */ false,
289                 /* translationX= */ 0f,
290                 /* layoutEnd= */ 10f,
291                 /* iconSize= */ 2f,
292         )
293         assertFalse(isOverflowing)
294     }
295 
296     @Test
297     fun isOverflowing_notLastChildXGreaterThanDotX_true() {
298         val isOverflowing = iconContainer.isOverflowing(
299                 /* isLastChild= */ false,
300                 /* translationX= */ 20f,
301                 /* layoutEnd= */ 10f,
302                 /* iconSize= */ 2f,
303         )
304         assertTrue(isOverflowing)
305     }
306 
307     @Test
308     fun isOverflowing_notLastChildXEqualToDotX_true() {
309         val isOverflowing = iconContainer.isOverflowing(
310                 /* isLastChild= */ false,
311                 /* translationX= */ 8f,
312                 /* layoutEnd= */ 10f,
313                 /* iconSize= */ 2f,
314         )
315         assertTrue(isOverflowing)
316     }
317 
318     private fun mockStatusBarIcon(): StatusBarIconView {
319         val iconView = mock(StatusBarIconView::class.java)
320         whenever(iconView.width).thenReturn(10)
321 
322         val icon = mock(android.graphics.drawable.Icon::class.java)
323         whenever(iconView.sourceIcon).thenReturn(icon)
324 
325         val sbn = mock(StatusBarNotification::class.java)
326         whenever(sbn.groupKey).thenReturn("groupKey")
327         whenever(iconView.notification).thenReturn(sbn)
328         return iconView
329     }
330 }