• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.launcher3
18 
19 import android.content.Context
20 import android.content.ContextWrapper
21 import android.graphics.Rect
22 import android.graphics.RectF
23 import android.view.View
24 import android.view.ViewGroup
25 import androidx.test.core.app.ApplicationProvider.getApplicationContext
26 import androidx.test.ext.junit.runners.AndroidJUnit4
27 import com.android.launcher3.util.ActivityContextWrapper
28 import kotlin.random.Random
29 import org.junit.Assert.assertArrayEquals
30 import org.junit.Assert.assertEquals
31 import org.junit.Assert.assertFalse
32 import org.junit.Assert.assertTrue
33 import org.junit.Before
34 import org.junit.Test
35 import org.junit.runner.RunWith
36 
37 @RunWith(AndroidJUnit4::class)
38 class UtilitiesTest {
39 
40     companion object {
41         const val SEED = 827
42     }
43 
44     private lateinit var mContext: Context
45 
46     @Before
setUpnull47     fun setUp() {
48         mContext = ActivityContextWrapper(getApplicationContext())
49     }
50 
51     @Test
testIsPropertyEnablednull52     fun testIsPropertyEnabled() {
53         // This assumes the property "propertyName" is not enabled by default
54         assertFalse(Utilities.isPropertyEnabled("propertyName"))
55     }
56 
57     @Test
testGetDescendantCoordRelativeToAncestornull58     fun testGetDescendantCoordRelativeToAncestor() {
59         val ancestor =
60             object : ViewGroup(mContext) {
61                 override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
62             }
63         val descendant = View(mContext)
64 
65         descendant.x = 50f
66         descendant.y = 30f
67         descendant.scaleX = 2f
68         descendant.scaleY = 2f
69 
70         ancestor.addView(descendant)
71 
72         val coord = floatArrayOf(10f, 15f)
73         val scale =
74             Utilities.getDescendantCoordRelativeToAncestor(descendant, ancestor, coord, false)
75 
76         assertEquals(2f, scale) // Expecting scale to be 2f
77         assertEquals(70f, coord[0])
78         assertEquals(60f, coord[1])
79     }
80 
81     @Test
testRoundArraynull82     fun testRoundArray() {
83         val floatArray = floatArrayOf(1.2f, 3.7f, 5.5f)
84         val intArray = IntArray(3)
85         Utilities.roundArray(floatArray, intArray)
86         assertArrayEquals(intArrayOf(1, 4, 6), intArray)
87     }
88 
89     @Test
testOffsetPointsnull90     fun testOffsetPoints() {
91         val points = floatArrayOf(1f, 2f, 3f, 4f)
92         Utilities.offsetPoints(points, 5f, 6f)
93 
94         val expected = listOf(6f, 8f, 8f, 10f)
95         assertEquals(expected, points.toList())
96     }
97 
98     @Test
testPointInViewnull99     fun testPointInView() {
100         val view = View(mContext)
101         view.layout(0, 0, 100, 100)
102 
103         assertTrue(Utilities.pointInView(view, 50f, 50f, 0f)) // Inside view
104         assertFalse(Utilities.pointInView(view, -10f, -10f, 0f)) // Outside view
105         assertTrue(Utilities.pointInView(view, -5f, -5f, 10f)) // Inside slop
106         assertFalse(Utilities.pointInView(view, 115f, 115f, 10f)) // Outside slop
107     }
108 
109     @Test
testNumberBoundingnull110     fun testNumberBounding() {
111         assertEquals(887.99f, Utilities.boundToRange(887.99f, 0f, 1000f))
112         assertEquals(2.777f, Utilities.boundToRange(887.99f, 0f, 2.777f))
113         assertEquals(900f, Utilities.boundToRange(887.99f, 900f, 1000f))
114 
115         assertEquals(9383667L, Utilities.boundToRange(9383667L, -999L, 9999999L))
116         assertEquals(9383668L, Utilities.boundToRange(9383667L, 9383668L, 9999999L))
117         assertEquals(42L, Utilities.boundToRange(9383667L, -999L, 42L))
118 
119         assertEquals(345, Utilities.boundToRange(345, 2, 500))
120         assertEquals(400, Utilities.boundToRange(345, 400, 500))
121         assertEquals(300, Utilities.boundToRange(345, 2, 300))
122 
123         val random = Random(SEED)
124         for (i in 1..300) {
125             val value = random.nextFloat()
126             val lowerBound = random.nextFloat()
127             val higherBound = lowerBound + random.nextFloat()
128 
129             assertEquals(
130                 "Utilities.boundToRange doesn't match Kotlin coerceIn",
131                 value.coerceIn(lowerBound, higherBound),
132                 Utilities.boundToRange(value, lowerBound, higherBound)
133             )
134             assertEquals(
135                 "Utilities.boundToRange doesn't match Kotlin coerceIn",
136                 value.toInt().coerceIn(lowerBound.toInt(), higherBound.toInt()),
137                 Utilities.boundToRange(value.toInt(), lowerBound.toInt(), higherBound.toInt())
138             )
139             assertEquals(
140                 "Utilities.boundToRange doesn't match Kotlin coerceIn",
141                 value.toLong().coerceIn(lowerBound.toLong(), higherBound.toLong()),
142                 Utilities.boundToRange(value.toLong(), lowerBound.toLong(), higherBound.toLong())
143             )
144             assertEquals(
145                 "If the lower bound is higher than lower bound, it should return the lower bound",
146                 higherBound,
147                 Utilities.boundToRange(value, higherBound, lowerBound)
148             )
149         }
150     }
151 
152     @Test
testTranslateOverlappingViewnull153     fun testTranslateOverlappingView() {
154         testConcentricOverlap()
155         leftDownCornerOverlap()
156         noOverlap()
157     }
158 
159     /*
160         Test Case: Rectangle Contained Within Another Rectangle
161 
162            +-------------+  <-- exclusionBounds
163            |             |
164            |   +-----+   |
165            |   |     |   |  <-- targetViewBounds
166            |   |     |   |
167            |   +-----+   |
168            |             |
169            +-------------+
170     */
testConcentricOverlapnull171     private fun testConcentricOverlap() {
172         val targetView = View(ContextWrapper(getApplicationContext()))
173         val targetViewBounds = Rect(40, 40, 60, 60)
174         val inclusionBounds = Rect(0, 0, 100, 100)
175         val exclusionBounds = Rect(30, 30, 70, 70)
176 
177         Utilities.translateOverlappingView(
178             targetView,
179             targetViewBounds,
180             inclusionBounds,
181             exclusionBounds,
182             Utilities.TRANSLATE_RIGHT
183         )
184         assertEquals(30f, targetView.translationX)
185         Utilities.translateOverlappingView(
186             targetView,
187             targetViewBounds,
188             inclusionBounds,
189             exclusionBounds,
190             Utilities.TRANSLATE_LEFT
191         )
192         assertEquals(-30f, targetView.translationX)
193         Utilities.translateOverlappingView(
194             targetView,
195             targetViewBounds,
196             inclusionBounds,
197             exclusionBounds,
198             Utilities.TRANSLATE_DOWN
199         )
200         assertEquals(30f, targetView.translationY)
201         Utilities.translateOverlappingView(
202             targetView,
203             targetViewBounds,
204             inclusionBounds,
205             exclusionBounds,
206             Utilities.TRANSLATE_UP
207         )
208         assertEquals(-30f, targetView.translationY)
209     }
210 
211     /*
212     Test Case: Non-Overlapping Rectangles
213 
214         +-----------------+      <-- targetViewBounds
215         |                 |
216         |                 |
217         +-----------------+
218 
219                  +-----------+     <-- exclusionBounds
220                  |           |
221                  |           |
222                  +-----------+
223     */
noOverlapnull224     private fun noOverlap() {
225         val targetView = View(ContextWrapper(getApplicationContext()))
226         val targetViewBounds = Rect(10, 10, 20, 20)
227 
228         val inclusionBounds = Rect(0, 0, 100, 100)
229         val exclusionBounds = Rect(30, 30, 40, 40)
230 
231         Utilities.translateOverlappingView(
232             targetView,
233             targetViewBounds,
234             inclusionBounds,
235             exclusionBounds,
236             Utilities.TRANSLATE_RIGHT
237         )
238         assertEquals(0f, targetView.translationX)
239         Utilities.translateOverlappingView(
240             targetView,
241             targetViewBounds,
242             inclusionBounds,
243             exclusionBounds,
244             Utilities.TRANSLATE_LEFT
245         )
246         assertEquals(0f, targetView.translationX)
247         Utilities.translateOverlappingView(
248             targetView,
249             targetViewBounds,
250             inclusionBounds,
251             exclusionBounds,
252             Utilities.TRANSLATE_DOWN
253         )
254         assertEquals(0f, targetView.translationY)
255         Utilities.translateOverlappingView(
256             targetView,
257             targetViewBounds,
258             inclusionBounds,
259             exclusionBounds,
260             Utilities.TRANSLATE_UP
261         )
262         assertEquals(0f, targetView.translationY)
263     }
264 
265     /*
266     Test Case: Rectangles Overlapping at Corners
267 
268        +------------+         <-- exclusionBounds
269        |            |
270     +-------+       |
271     |  |    |       |          <-- targetViewBounds
272     |  +------------+
273     |       |
274     +-------+
275     */
leftDownCornerOverlapnull276     private fun leftDownCornerOverlap() {
277         val targetView = View(ContextWrapper(getApplicationContext()))
278         val targetViewBounds = Rect(20, 20, 30, 30)
279 
280         val inclusionBounds = Rect(0, 0, 100, 100)
281         val exclusionBounds = Rect(25, 25, 35, 35)
282 
283         Utilities.translateOverlappingView(
284             targetView,
285             targetViewBounds,
286             inclusionBounds,
287             exclusionBounds,
288             Utilities.TRANSLATE_RIGHT
289         )
290         assertEquals(15f, targetView.translationX)
291         Utilities.translateOverlappingView(
292             targetView,
293             targetViewBounds,
294             inclusionBounds,
295             exclusionBounds,
296             Utilities.TRANSLATE_LEFT
297         )
298         assertEquals(-5f, targetView.translationX)
299         Utilities.translateOverlappingView(
300             targetView,
301             targetViewBounds,
302             inclusionBounds,
303             exclusionBounds,
304             Utilities.TRANSLATE_DOWN
305         )
306         assertEquals(15f, targetView.translationY)
307         Utilities.translateOverlappingView(
308             targetView,
309             targetViewBounds,
310             inclusionBounds,
311             exclusionBounds,
312             Utilities.TRANSLATE_UP
313         )
314         assertEquals(-5f, targetView.translationY)
315     }
316 
317     @Test
trimnull318     fun trim() {
319         val expectedString = "Hello World"
320         assertEquals(expectedString, Utilities.trim("Hello World   "))
321         // Basic trimming
322         assertEquals(expectedString, Utilities.trim("   Hello World  "))
323         assertEquals(expectedString, Utilities.trim("   Hello World"))
324 
325         // Non-breaking whitespace
326         assertEquals("Hello World", Utilities.trim("\u00A0\u00A0Hello World\u00A0\u00A0"))
327 
328         // Whitespace combinations
329         assertEquals(expectedString, Utilities.trim("\t \r\n Hello World \n\r"))
330         assertEquals(expectedString, Utilities.trim("\nHello World   "))
331 
332         // Null input
333         assertEquals("", Utilities.trim(null))
334 
335         // Empty String
336         assertEquals("", Utilities.trim(""))
337     }
338 
339     @Test
getProgressnull340     fun getProgress() {
341         // Basic test
342         assertEquals(0.5f, Utilities.getProgress(50f, 0f, 100f), 0.001f)
343 
344         // Negative values
345         assertEquals(0.5f, Utilities.getProgress(-20f, -50f, 10f), 0.001f)
346 
347         // Outside of range
348         assertEquals(1.2f, Utilities.getProgress(120f, 0f, 100f), 0.001f)
349     }
350 
351     @Test
scaleRectFAboutPivotnull352     fun scaleRectFAboutPivot() {
353         // Enlarge
354         var rectF = RectF(10f, 20f, 50f, 80f)
355         Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.5f)
356         assertEquals(RectF(0f, 5f, 60f, 95f), rectF)
357 
358         // Shrink
359         rectF = RectF(10f, 20f, 50f, 80f)
360         Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 0.5f)
361         assertEquals(RectF(20f, 35f, 40f, 65f), rectF)
362 
363         // No scale
364         rectF = RectF(10f, 20f, 50f, 80f)
365         Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.0f)
366         assertEquals(RectF(10f, 20f, 50f, 80f), rectF)
367     }
368 
369     @Test
rotateBoundsnull370     fun rotateBounds() {
371         var rect = Rect(20, 70, 60, 80)
372         Utilities.rotateBounds(rect, 100, 100, 0)
373         assertEquals(Rect(20, 70, 60, 80), rect)
374 
375         rect = Rect(20, 70, 60, 80)
376         Utilities.rotateBounds(rect, 100, 100, 1)
377         assertEquals(Rect(70, 40, 80, 80), rect)
378 
379         // case removed for b/28435189
380         //        rect = Rect(20, 70, 60, 80)
381         //        Utilities.rotateBounds(rect, 100, 100, 2)
382         //        assertEquals(Rect(40, 20, 80, 30), rect)
383 
384         rect = Rect(20, 70, 60, 80)
385         Utilities.rotateBounds(rect, 100, 100, 3)
386         assertEquals(Rect(20, 20, 30, 60), rect)
387     }
388 }
389