diff --git a/tests/Android.bp b/tests/Android.bp index c329eceead..3670c37add 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -23,7 +23,7 @@ package { // Source code used for test filegroup { name: "launcher-tests-src", - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: ["src/**/*.java"], } // Source code used for oop test helpers diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java new file mode 100644 index 0000000000..8a4590a388 --- /dev/null +++ b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.java @@ -0,0 +1,201 @@ +package com.android.launcher3.model; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.util.Pair; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.model.BgDataModel.Callbacks; +import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.Executors; +import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.IntArray; +import com.android.launcher3.util.IntSparseArrayMap; +import com.android.launcher3.util.LauncherModelHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link AddWorkspaceItemsTask} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class AddWorkspaceItemsTaskTest { + + private final ComponentName mComponent1 = new ComponentName("a", "b"); + private final ComponentName mComponent2 = new ComponentName("b", "b"); + + private Context mTargetContext; + private InvariantDeviceProfile mIdp; + private LauncherAppState mAppState; + private LauncherModelHelper mModelHelper; + + private IntArray mExistingScreens; + private IntArray mNewScreens; + private IntSparseArrayMap mScreenOccupancy; + + @Before + public void setup() { + mModelHelper = new LauncherModelHelper(); + mTargetContext = mModelHelper.sandboxContext; + mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); + mIdp.numColumns = mIdp.numRows = 5; + mAppState = LauncherAppState.getInstance(mTargetContext); + + mExistingScreens = new IntArray(); + mScreenOccupancy = new IntSparseArrayMap<>(); + mNewScreens = new IntArray(); + } + + @After + public void tearDown() { + mModelHelper.destroy(); + } + + private AddWorkspaceItemsTask newTask(ItemInfo... items) { + List> list = new ArrayList<>(); + for (ItemInfo item : items) { + list.add(Pair.create(item, null)); + } + return new AddWorkspaceItemsTask(list); + } + + @Test + public void testFindSpaceForItem_prefers_second() throws Exception { + // First screen has only one hole of size 1 + int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + // Second screen has 2 holes of sizes 3x2 and 2x3 + setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); + + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); + assertEquals(1, spaceFound[0]); + assertTrue(mScreenOccupancy.get(spaceFound[0]) + .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); + + // Find a larger space + spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3); + assertEquals(2, spaceFound[0]); + assertTrue(mScreenOccupancy.get(spaceFound[0]) + .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)); + } + + @Test + public void testFindSpaceForItem_adds_new_screen() throws Exception { + // First screen has 2 holes of sizes 3x2 and 2x3 + setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); + + IntArray oldScreens = mExistingScreens.clone(); + int[] spaceFound = newTask().findSpaceForItem( + mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3); + assertFalse(oldScreens.contains(spaceFound[0])); + assertTrue(mNewScreens.contains(spaceFound[0])); + } + + @Test + public void testAddItem_existing_item_ignored() throws Exception { + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + + // Setup a screen with a hole + setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + // Nothing was added + assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty()); + } + + @Test + public void testAddItem_some_items_added() throws Exception { + Callbacks callbacks = mock(Callbacks.class); + Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get(); + + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + + WorkspaceItemInfo info2 = new WorkspaceItemInfo(); + info2.intent = new Intent().setComponent(mComponent2); + + // Setup a screen with a hole + setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); + + mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run(); + ArgumentCaptor notAnimated = ArgumentCaptor.forClass(ArrayList.class); + ArgumentCaptor animated = ArgumentCaptor.forClass(ArrayList.class); + + // only info2 should be added because info was already added to the workspace + // in setupWorkspaceWithHoles() + verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(), + animated.capture()); + assertTrue(notAnimated.getValue().isEmpty()); + + assertEquals(1, animated.getValue().size()); + assertTrue(animated.getValue().contains(info2)); + } + + private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception { + return mModelHelper.executeSimpleTask( + model -> writeWorkspaceWithHoles(model, startId, screenId, holes)); + } + + private int writeWorkspaceWithHoles( + BgDataModel bgDataModel, int startId, int screenId, Rect... holes) { + GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows); + occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true); + for (Rect r : holes) { + occupancy.markCells(r, false); + } + + mExistingScreens.add(screenId); + mScreenOccupancy.append(screenId, occupancy); + + for (int x = 0; x < mIdp.numColumns; x++) { + for (int y = 0; y < mIdp.numRows; y++) { + if (!occupancy.cells[x][y]) { + continue; + } + + WorkspaceItemInfo info = new WorkspaceItemInfo(); + info.intent = new Intent().setComponent(mComponent1); + info.id = startId++; + info.screenId = screenId; + info.cellX = x; + info.cellY = y; + info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; + bgDataModel.addItem(mTargetContext, info, false); + + ContentWriter writer = new ContentWriter(mTargetContext); + info.writeToValues(writer); + writer.put(Favorites._ID, info.id); + mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI, + writer.getValues(mTargetContext)); + } + } + return startId; + } +} diff --git a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt deleted file mode 100644 index e315658d37..0000000000 --- a/tests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt +++ /dev/null @@ -1,325 +0,0 @@ -/* - * Copyright (C) 2021 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.model - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.graphics.Rect -import android.util.Pair -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.launcher3.InvariantDeviceProfile -import com.android.launcher3.LauncherAppState -import com.android.launcher3.LauncherSettings -import com.android.launcher3.model.data.ItemInfo -import com.android.launcher3.model.data.WorkspaceItemInfo -import com.android.launcher3.util.* -import com.android.launcher3.util.IntArray -import org.junit.After -import org.junit.Assert.* -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.* -import org.mockito.ArgumentMatchers.any -import org.mockito.Mockito.verify -import kotlin.collections.ArrayList - -/** - * Tests for [AddWorkspaceItemsTask] - */ -@SmallTest -@RunWith(AndroidJUnit4::class) -class AddWorkspaceItemsTaskTest { - - @Captor - private lateinit var animatedItemArgumentCaptor: ArgumentCaptor> - - @Captor - private lateinit var notAnimatedItemArgumentCaptor: ArgumentCaptor> - - @Mock - private lateinit var dataModelCallbacks: BgDataModel.Callbacks - - private lateinit var mTargetContext: Context - private lateinit var mIdp: InvariantDeviceProfile - private lateinit var mAppState: LauncherAppState - private lateinit var mModelHelper: LauncherModelHelper - private lateinit var mExistingScreens: IntArray - private lateinit var mNewScreens: IntArray - private lateinit var mScreenOccupancy: IntSparseArrayMap - - private val emptyScreenHoles = listOf(Rect(0, 0, 5, 5)) - private val fullScreenHoles = emptyList() - - - @Before - fun setup() { - MockitoAnnotations.initMocks(this) - mModelHelper = LauncherModelHelper() - mTargetContext = mModelHelper.sandboxContext - mIdp = InvariantDeviceProfile.INSTANCE[mTargetContext] - mIdp.numRows = 5 - mIdp.numColumns = mIdp.numRows - mAppState = LauncherAppState.getInstance(mTargetContext) - mExistingScreens = IntArray() - mScreenOccupancy = IntSparseArrayMap() - mNewScreens = IntArray() - Executors.MAIN_EXECUTOR.submit { mModelHelper.model.addCallbacks(dataModelCallbacks) }.get() - } - - @After - fun tearDown() { - mModelHelper.destroy() - } - - @Test - fun justEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnFirstScreenId() { - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - // 2 holes of sizes 3x2 and 2x3 - screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - ) - - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 1, 1) - assertEquals(1, spaceFound[0]) - assertTrue(mScreenOccupancy[spaceFound[0]] - .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)) - } - - @Test - fun notEnoughSpaceOnFirstScreen_whenFindSpaceForItem_thenReturnSecondScreenId() { - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - // 2 holes of sizes 3x2 and 2x3 - screen2 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - ) - - // Find a larger space - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 2, 3) - assertEquals(2, spaceFound[0]) - assertTrue(mScreenOccupancy[spaceFound[0]] - .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)) - } - - @Test - fun notEnoughSpaceOnExistingScreens_whenFindSpaceForItem_thenReturnNewScreenId() { - setupWorkspacesWithHoles( - // 2 holes of sizes 3x2 and 2x3 - screen1 = listOf(Rect(2, 0, 5, 2), Rect(0, 2, 2, 5)), - // 2 holes of sizes 1x2 and 2x2 - screen2 = listOf(Rect(1, 0, 2, 2), Rect(3, 2, 5, 4)), - ) - - val oldScreens = mExistingScreens.clone() - val spaceFound = newTask().findSpaceForItem( - mAppState, mModelHelper.bgDataModel, mExistingScreens, mNewScreens, 3, 3) - assertFalse(oldScreens.contains(spaceFound[0])) - assertTrue(mNewScreens.contains(spaceFound[0])) - } - - @Test - fun enoughSpaceOnFirstScreen_whenTaskRuns_thenAddItemToFirstScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 space - screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(1, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstPageIsFull_whenTaskRuns_thenAddItemToSecondScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(2, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstScreenIsEmptyButSecondIsNotEmpty_whenTaskRuns_thenAddItemToSecondScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = emptyScreenHoles, - screen2 = listOf(Rect(2, 0, 5, 2)), // 3x2 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(2, addedItems.first().itemInfo.screenId) - } - - @Test - fun twoEmptyMiddleScreens_whenTaskRuns_thenAddItemToThirdScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = emptyScreenHoles, - screen2 = emptyScreenHoles, - screen3 = listOf(Rect(1, 1, 4, 4)), // 3x3 space - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun allPagesAreFull_whenTaskRuns_thenAddItemToNewScreen() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - screen2 = fullScreenHoles, - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun firstTwoPagesAreFull_and_ThirdPageIsEmpty_whenTaskRuns_thenAddItemToThirdPage() { - val workspaceHoles = createWorkspaceHoles( - screen1 = fullScreenHoles, - screen2 = fullScreenHoles, - screen3 = emptyScreenHoles - ) - val addedItems = testAddItems(workspaceHoles, getNewItem()) - assertEquals(1, addedItems.size) - assertEquals(3, addedItems.first().itemInfo.screenId) - } - - @Test - fun itemIsAlreadyAdded_whenTaskRun_thenIgnoreItem() { - val task = newTask(getExistingItem()) - setupWorkspacesWithHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - ) - - // Nothing was added - assertTrue(mModelHelper.executeTaskForTest(task).isEmpty()) - } - - @Test - fun newAndExistingItems_whenTaskRun_thenAddOnlyTheNewOne() { - val newItem = getNewItem() - val workspaceHoles = createWorkspaceHoles( - screen1 = listOf(Rect(2, 2, 3, 3)), // 1x1 hole - ) - val addedItems = testAddItems(workspaceHoles, getExistingItem(), newItem) - assertEquals(1, addedItems.size) - val addedItem = addedItems.first() - assert(addedItem.isAnimated) - val addedItemInfo = addedItem.itemInfo - assertEquals(1, addedItemInfo.screenId) - assertEquals(newItem, addedItemInfo) - } - - private fun testAddItems( - workspaceHoles: List>, - vararg itemsToAdd: WorkspaceItemInfo - ): List { - setupWorkspaces(workspaceHoles) - mModelHelper.executeTaskForTest(newTask(*itemsToAdd))[0].run() - - verify(dataModelCallbacks).bindAppsAdded(any(), - notAnimatedItemArgumentCaptor.capture(), animatedItemArgumentCaptor.capture()) - - val addedItems = mutableListOf() - addedItems.addAll(animatedItemArgumentCaptor.value.map { AddedItem(it, true) }) - addedItems.addAll(notAnimatedItemArgumentCaptor.value.map { AddedItem(it, false) }) - return addedItems - } - - private fun setupWorkspaces(workspaceHoles: List>) { - var nextItemId = 1 - var screenId = 1 - workspaceHoles.forEach { holes -> - nextItemId = setupWorkspace(nextItemId, screenId++, *holes.toTypedArray()) - } - } - - private fun setupWorkspace(startId: Int, screenId: Int, vararg holes: Rect): Int { - return mModelHelper.executeSimpleTask { dataModel -> - writeWorkspaceWithHoles(dataModel, startId, screenId, *holes) - } - } - - private fun writeWorkspaceWithHoles( - bgDataModel: BgDataModel, - itemStartId: Int, - screenId: Int, - vararg holes: Rect, - ): Int { - var itemId = itemStartId - val occupancy = GridOccupancy(mIdp.numColumns, mIdp.numRows) - occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true) - holes.forEach { holeRect -> - occupancy.markCells(holeRect, false) - } - mExistingScreens.add(screenId) - mScreenOccupancy.append(screenId, occupancy) - for (x in 0 until mIdp.numColumns) { - for (y in 0 until mIdp.numRows) { - if (!occupancy.cells[x][y]) { - continue - } - val info = getExistingItem() - info.id = itemId++ - info.screenId = screenId - info.cellX = x - info.cellY = y - info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP - bgDataModel.addItem(mTargetContext, info, false) - val writer = ContentWriter(mTargetContext) - info.writeToValues(writer) - writer.put(LauncherSettings.Favorites._ID, info.id) - mTargetContext.contentResolver.insert(LauncherSettings.Favorites.CONTENT_URI, - writer.getValues(mTargetContext)) - } - } - return itemId - } - - private fun setupWorkspacesWithHoles( - screen1: List? = null, - screen2: List? = null, - screen3: List? = null, - ) = createWorkspaceHoles(screen1, screen2, screen3) - .let(this::setupWorkspaces) - - private fun createWorkspaceHoles( - screen1: List? = null, - screen2: List? = null, - screen3: List? = null, - ): List> = listOfNotNull(screen1, screen2, screen3) - - private fun newTask(vararg items: ItemInfo): AddWorkspaceItemsTask = - items.map { Pair.create(it, Any()) } - .toMutableList() - .let(::AddWorkspaceItemsTask) - - private fun getExistingItem() = WorkspaceItemInfo() - .apply { intent = Intent().setComponent(ComponentName("a", "b")) } - - private fun getNewItem() = WorkspaceItemInfo() - .apply { intent = Intent().setComponent(ComponentName("b", "b")) } -} - -private data class AddedItem( - val itemInfo: ItemInfo, - val isAnimated: Boolean -) \ No newline at end of file