Adding testin for Utilities.java
Happy testing week.
Bug: 353588686
Test: LauncherUtilitiesUnitTest
Flag: TEST_ONLY
Change-Id: I321aa9197a53801282591198aef0229d44da60d3
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index a296f46..a448228 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -106,8 +106,6 @@
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Various utilities shared amongst the Launcher's classes.
@@ -116,8 +114,7 @@
private static final String TAG = "Launcher.Utilities";
- private static final Pattern sTrimPattern =
- Pattern.compile("^[\\s|\\p{javaSpaceChar}]*(.*)[\\s|\\p{javaSpaceChar}]*$");
+ private static final String TRIM_PATTERN = "(^\\h+|\\h+$)";
private static final Matrix sMatrix = new Matrix();
private static final Matrix sInverseMatrix = new Matrix();
@@ -445,10 +442,7 @@
if (s == null) {
return "";
}
-
- // Just strip any sequence of whitespace or java space characters from the beginning and end
- Matcher m = sTrimPattern.matcher(s);
- return m.replaceAll("$1");
+ return s.toString().replaceAll(TRIM_PATTERN, "").trim();
}
/**
@@ -722,14 +716,59 @@
}
/**
- * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CCW. Parent
+ * Rotates `inOutBounds` by `delta` 90-degree increments. Rotation is visually CW. Parent
* sizes represent the "space" that will rotate carrying inOutBounds along with it to determine
* the final bounds.
+ *
+ * As an example if this is the input:
+ * +-------------+
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * | |
+ * | |
+ * | |
+ * +-------------+
+ * This would be case delta % 4 == 0:
+ * +-------------+
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * | |
+ * | |
+ * | |
+ * +-------------+
+ * This would be case delta % 4 == 1:
+ * +----------------+
+ * | +--+ |
+ * | | | |
+ * | | | |
+ * | +--+ |
+ * | |
+ * +----------------+
+ * This would be case delta % 4 == 2:
+ * +-------------+
+ * | |
+ * | |
+ * | |
+ * | +-----+ |
+ * | | | |
+ * | +-----+ |
+ * +-------------+
+ * This would be case delta % 4 == 3:
+ * +----------------+
+ * | +--+ |
+ * | | | |
+ * | | | |
+ * | +--+ |
+ * | |
+ * +----------------+
*/
public static void rotateBounds(Rect inOutBounds, int parentWidth, int parentHeight,
int delta) {
int rdelta = ((delta % 4) + 4) % 4;
int origLeft = inOutBounds.left;
+ int origTop = inOutBounds.top;
switch (rdelta) {
case 0:
return;
@@ -741,6 +780,8 @@
return;
case 2:
inOutBounds.left = parentWidth - inOutBounds.right;
+ inOutBounds.top = parentHeight - inOutBounds.bottom;
+ inOutBounds.bottom = parentHeight - origTop;
inOutBounds.right = parentWidth - origLeft;
return;
case 3:
@@ -830,6 +871,9 @@
@NonNull Rect inclusionBounds,
@NonNull Rect exclusionBounds,
@AdjustmentDirection int adjustmentDirection) {
+ if (!Rect.intersects(targetViewBounds, exclusionBounds)) {
+ return;
+ }
switch (adjustmentDirection) {
case TRANSLATE_RIGHT:
targetView.setTranslationX(Math.min(
diff --git a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
index 60a4197..5a26087 100644
--- a/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesTest.kt
@@ -17,12 +17,19 @@
package com.android.launcher3
import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Rect
+import android.graphics.RectF
import android.view.View
import android.view.ViewGroup
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.util.ActivityContextWrapper
-import org.junit.Assert.*
+import kotlin.random.Random
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,6 +37,10 @@
@RunWith(AndroidJUnit4::class)
class UtilitiesTest {
+ companion object {
+ const val SEED = 827
+ }
+
private lateinit var mContext: Context
@Before
@@ -94,4 +105,283 @@
assertTrue(Utilities.pointInView(view, -5f, -5f, 10f)) // Inside slop
assertFalse(Utilities.pointInView(view, 115f, 115f, 10f)) // Outside slop
}
+
+ @Test
+ fun testNumberBounding() {
+ assertEquals(887.99f, Utilities.boundToRange(887.99f, 0f, 1000f))
+ assertEquals(2.777f, Utilities.boundToRange(887.99f, 0f, 2.777f))
+ assertEquals(900f, Utilities.boundToRange(887.99f, 900f, 1000f))
+
+ assertEquals(9383667L, Utilities.boundToRange(9383667L, -999L, 9999999L))
+ assertEquals(9383668L, Utilities.boundToRange(9383667L, 9383668L, 9999999L))
+ assertEquals(42L, Utilities.boundToRange(9383667L, -999L, 42L))
+
+ assertEquals(345, Utilities.boundToRange(345, 2, 500))
+ assertEquals(400, Utilities.boundToRange(345, 400, 500))
+ assertEquals(300, Utilities.boundToRange(345, 2, 300))
+
+ val random = Random(SEED)
+ for (i in 1..300) {
+ val value = random.nextFloat()
+ val lowerBound = random.nextFloat()
+ val higherBound = lowerBound + random.nextFloat()
+
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.coerceIn(lowerBound, higherBound),
+ Utilities.boundToRange(value, lowerBound, higherBound)
+ )
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.toInt().coerceIn(lowerBound.toInt(), higherBound.toInt()),
+ Utilities.boundToRange(value.toInt(), lowerBound.toInt(), higherBound.toInt())
+ )
+ assertEquals(
+ "Utilities.boundToRange doesn't match Kotlin coerceIn",
+ value.toLong().coerceIn(lowerBound.toLong(), higherBound.toLong()),
+ Utilities.boundToRange(value.toLong(), lowerBound.toLong(), higherBound.toLong())
+ )
+ assertEquals(
+ "If the lower bound is higher than lower bound, it should return the lower bound",
+ higherBound,
+ Utilities.boundToRange(value, higherBound, lowerBound)
+ )
+ }
+ }
+
+ @Test
+ fun testTranslateOverlappingView() {
+ testConcentricOverlap()
+ leftDownCornerOverlap()
+ noOverlap()
+ }
+
+ /*
+ Test Case: Rectangle Contained Within Another Rectangle
+
+ +-------------+ <-- exclusionBounds
+ | |
+ | +-----+ |
+ | | | | <-- targetViewBounds
+ | | | |
+ | +-----+ |
+ | |
+ +-------------+
+ */
+ private fun testConcentricOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(40, 40, 60, 60)
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(30, 30, 70, 70)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(30f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(-30f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(30f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(-30f, targetView.translationY)
+ }
+
+ /*
+ Test Case: Non-Overlapping Rectangles
+
+ +-----------------+ <-- targetViewBounds
+ | |
+ | |
+ +-----------------+
+
+ +-----------+ <-- exclusionBounds
+ | |
+ | |
+ +-----------+
+ */
+ private fun noOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(10, 10, 20, 20)
+
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(30, 30, 40, 40)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(0f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(0f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(0f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(0f, targetView.translationY)
+ }
+
+ /*
+ Test Case: Rectangles Overlapping at Corners
+
+ +------------+ <-- exclusionBounds
+ | |
+ +-------+ |
+ | | | | <-- targetViewBounds
+ | +------------+
+ | |
+ +-------+
+ */
+ private fun leftDownCornerOverlap() {
+ val targetView = View(ContextWrapper(getApplicationContext()))
+ val targetViewBounds = Rect(20, 20, 30, 30)
+
+ val inclusionBounds = Rect(0, 0, 100, 100)
+ val exclusionBounds = Rect(25, 25, 35, 35)
+
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_RIGHT
+ )
+ assertEquals(15f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_LEFT
+ )
+ assertEquals(-5f, targetView.translationX)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_DOWN
+ )
+ assertEquals(15f, targetView.translationY)
+ Utilities.translateOverlappingView(
+ targetView,
+ targetViewBounds,
+ inclusionBounds,
+ exclusionBounds,
+ Utilities.TRANSLATE_UP
+ )
+ assertEquals(-5f, targetView.translationY)
+ }
+
+ @Test
+ fun trim() {
+ val expectedString = "Hello World"
+ assertEquals(expectedString, Utilities.trim("Hello World "))
+ // Basic trimming
+ assertEquals(expectedString, Utilities.trim(" Hello World "))
+ assertEquals(expectedString, Utilities.trim(" Hello World"))
+
+ // Non-breaking whitespace
+ assertEquals("Hello World", Utilities.trim("\u00A0\u00A0Hello World\u00A0\u00A0"))
+
+ // Whitespace combinations
+ assertEquals(expectedString, Utilities.trim("\t \r\n Hello World \n\r"))
+ assertEquals(expectedString, Utilities.trim("\nHello World "))
+
+ // Null input
+ assertEquals("", Utilities.trim(null))
+
+ // Empty String
+ assertEquals("", Utilities.trim(""))
+ }
+
+ @Test
+ fun getProgress() {
+ // Basic test
+ assertEquals(0.5f, Utilities.getProgress(50f, 0f, 100f), 0.001f)
+
+ // Negative values
+ assertEquals(0.5f, Utilities.getProgress(-20f, -50f, 10f), 0.001f)
+
+ // Outside of range
+ assertEquals(1.2f, Utilities.getProgress(120f, 0f, 100f), 0.001f)
+ }
+
+ @Test
+ fun scaleRectFAboutPivot() {
+ // Enlarge
+ var rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.5f)
+ assertEquals(RectF(0f, 5f, 60f, 95f), rectF)
+
+ // Shrink
+ rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 0.5f)
+ assertEquals(RectF(20f, 35f, 40f, 65f), rectF)
+
+ // No scale
+ rectF = RectF(10f, 20f, 50f, 80f)
+ Utilities.scaleRectFAboutPivot(rectF, 30f, 50f, 1.0f)
+ assertEquals(RectF(10f, 20f, 50f, 80f), rectF)
+ }
+
+ @Test
+ fun rotateBounds() {
+ var rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 0)
+ assertEquals(Rect(20, 70, 60, 80), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 1)
+ assertEquals(Rect(70, 40, 80, 80), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 2)
+ assertEquals(Rect(40, 20, 80, 30), rect)
+
+ rect = Rect(20, 70, 60, 80)
+ Utilities.rotateBounds(rect, 100, 100, 3)
+ assertEquals(Rect(20, 20, 30, 60), rect)
+ }
}