Adding unit test to makeSpaceForHotseatMigration
Flag: NA
Bug: 318417510
Test: atest HotseatReorderUnitTest
Change-Id: If4fe388d00138b2d43693df0cf79abb16f354291
diff --git a/tests/Android.bp b/tests/Android.bp
index 310e418..ed8609e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -176,6 +176,7 @@
name: "launcher-testing-shared",
srcs: [
"multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
+ "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt"
],
resource_dirs: [],
manifest: "multivalentTests/shared/AndroidManifest.xml",
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
index dbbdcf5..62f2259 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/CellLayoutBoard.java
@@ -66,7 +66,7 @@
}
public CellLayoutBoard(int width, int height) {
- mWidget = new char[width + 1][height + 1];
+ mWidget = new char[width][height];
this.mWidth = width;
this.mHeight = height;
for (int x = 0; x < mWidget.length; x++) {
@@ -371,8 +371,8 @@
s.append("\n");
maxX = Math.min(maxX, mWidget.length);
maxY = Math.min(maxY, mWidget[0].length);
- for (int y = 0; y <= maxY; y++) {
- for (int x = 0; x <= maxX; x++) {
+ for (int y = 0; y < maxY; y++) {
+ for (int x = 0; x < maxX; x++) {
s.append(mWidget[x][y]);
}
s.append('\n');
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
index 770024f..fcfb3db 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/RandomBoardGenerator.kt
@@ -27,7 +27,7 @@
* usually less than 100.
* @return a randomly generated board filled with icons and widgets.
*/
- open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard? {
+ open fun generateBoard(width: Int, height: Int, remainingEmptySpaces: Int): CellLayoutBoard {
val cellLayoutBoard = CellLayoutBoard(width, height)
return fillBoard(cellLayoutBoard, Rect(0, 0, width, height), remainingEmptySpaces)
}
@@ -39,8 +39,8 @@
): CellLayoutBoard {
var remainingEmptySpaces = remainingEmptySpacesArg
if (area.height() * area.width() <= 0) return board
- val width = getRandom(1, area.width() - 1)
- val height = getRandom(1, area.height() - 1)
+ val width = getRandom(1, area.width())
+ val height = getRandom(1, area.height())
val x = area.left + getRandom(0, area.width() - width)
val y = area.top + getRandom(0, area.height() - height)
if (remainingEmptySpaces > 0) {
diff --git a/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
new file mode 100644
index 0000000..13dfd5e
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/HotseatReorderUnitTest.kt
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.celllayout
+
+import android.content.Context
+import android.graphics.Point
+import android.util.Log
+import android.view.View
+import androidx.core.view.get
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.CellLayout
+import com.android.launcher3.celllayout.board.CellLayoutBoard
+import com.android.launcher3.celllayout.board.IconPoint
+import com.android.launcher3.celllayout.board.PermutedBoardComparator
+import com.android.launcher3.celllayout.board.WidgetRect
+import com.android.launcher3.celllayout.testgenerator.RandomBoardGenerator
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.views.DoubleShadowBubbleTextView
+import java.util.Random
+import org.junit.Assert
+import org.junit.Rule
+import org.junit.Test
+
+private class HotseatReorderTestCase(
+ val startBoard: CellLayoutBoard,
+ val endBoard: CellLayoutBoard
+) {
+ override fun toString(): String {
+ return "$startBoard#endBoard:\n$endBoard"
+ }
+}
+
+class HotseatReorderUnitTest {
+
+ private val applicationContext: Context =
+ ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+ @JvmField @Rule var cellLayoutBuilder = UnitTestCellLayoutBuilderRule()
+
+ /**
+ * This test generates random CellLayout configurations and then try to reorder it and makes
+ * sure the result is a valid board meaning it didn't remove any widget or icon.
+ */
+ @Test
+ fun generateValidTests() {
+ val generator = Random(Companion.SEED.toLong())
+ for (i in 0 until Companion.TOTAL_OF_CASES_GENERATED) {
+ // Using a new seed so that we can replicate the same test cases.
+ val seed = generator.nextInt()
+ Log.d(Companion.TAG, "Seed = $seed")
+
+ val testCase: HotseatReorderTestCase =
+ generateRandomTestCase(RandomBoardGenerator(Random(seed.toLong())))
+ Log.d(Companion.TAG, "testCase = $testCase")
+
+ Assert.assertTrue(
+ "invalid case $i",
+ PermutedBoardComparator().compare(testCase.startBoard, testCase.endBoard) == 0
+ )
+ }
+ }
+
+ private fun addViewInCellLayout(
+ cellLayout: CellLayout,
+ cellX: Int,
+ cellY: Int,
+ spanX: Int,
+ spanY: Int,
+ isWidget: Boolean
+ ) {
+ val cell =
+ if (isWidget) View(applicationContext)
+ else DoubleShadowBubbleTextView(applicationContext)
+ cell.layoutParams = CellLayoutLayoutParams(cellX, cellY, spanX, spanY)
+ cellLayout.addViewToCellLayout(
+ cell,
+ -1,
+ cell.id,
+ cell.layoutParams as CellLayoutLayoutParams,
+ true
+ )
+ }
+
+ private fun solve(board: CellLayoutBoard): CellLayout {
+ val cl = cellLayoutBuilder.createCellLayout(board.width, board.height, false)
+ // The views have to be sorted or the result can vary
+ board.icons
+ .map(IconPoint::getCoord)
+ .sortedWith(
+ Comparator.comparing { p: Any -> (p as Point).x }
+ .thenComparing { p: Any -> (p as Point).y }
+ )
+ .forEach { p ->
+ addViewInCellLayout(
+ cellLayout = cl,
+ cellX = p.x,
+ cellY = p.y,
+ spanX = 1,
+ spanY = 1,
+ isWidget = false
+ )
+ }
+ board.widgets
+ .sortedWith(
+ Comparator.comparing(WidgetRect::getCellX).thenComparing(WidgetRect::getCellY)
+ )
+ .forEach { widget ->
+ addViewInCellLayout(
+ cl,
+ widget.cellX,
+ widget.cellY,
+ widget.spanX,
+ widget.spanY,
+ isWidget = true
+ )
+ }
+ if (cl.makeSpaceForHotseatMigration(true)) {
+ commitTempPosition(cl)
+ }
+ return cl
+ }
+
+ private fun commitTempPosition(cellLayout: CellLayout) {
+ val count = cellLayout.shortcutsAndWidgets.childCount
+ for (i in 0 until count) {
+ val params = cellLayout.shortcutsAndWidgets[i].layoutParams as CellLayoutLayoutParams
+ params.cellX = params.tmpCellX
+ params.cellY = params.tmpCellY
+ }
+ }
+
+ private fun boardFromCellLayout(cellLayout: CellLayout): CellLayoutBoard {
+ val views = mutableListOf<View>()
+ for (i in 0 until cellLayout.shortcutsAndWidgets.childCount) {
+ views.add(cellLayout.shortcutsAndWidgets.getChildAt(i))
+ }
+ return CellLayoutTestUtils.viewsToBoard(views, cellLayout.countX, cellLayout.countY)
+ }
+
+ private fun generateRandomTestCase(
+ boardGenerator: RandomBoardGenerator
+ ): HotseatReorderTestCase {
+ val width: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
+ val height: Int = boardGenerator.getRandom(3, Companion.MAX_BOARD_SIZE)
+ val targetWidth: Int = boardGenerator.getRandom(1, width - 2)
+ val targetHeight: Int = boardGenerator.getRandom(1, height - 2)
+ val board: CellLayoutBoard =
+ boardGenerator.generateBoard(width, height, targetWidth * targetHeight)
+ val finishBoard: CellLayoutBoard = boardFromCellLayout(solve(board))
+ return HotseatReorderTestCase(board, finishBoard)
+ }
+
+ companion object {
+ private const val MAX_BOARD_SIZE = 13
+
+ /**
+ * There is nothing special about this numbers, the random seed is just to be able to
+ * reproduce the test cases and the height and width is a random number similar to what
+ * users expect on their devices
+ */
+ private const val SEED = -194162315
+ private const val TOTAL_OF_CASES_GENERATED = 300
+ private const val TAG = "HotseatReorderUnitTest"
+ }
+}