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.verify; import android.content.ComponentName; import android.content.Intent; import android.graphics.Rect; import android.util.Pair; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.WorkspaceItemInfo; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.IntArray; import com.android.launcher3.util.IntSparseArrayMap; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.robolectric.RobolectricTestRunner; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Tests for {@link AddWorkspaceItemsTask} */ @RunWith(RobolectricTestRunner.class) public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase { private final ComponentName mComponent1 = new ComponentName("a", "b"); private final ComponentName mComponent2 = new ComponentName("b", "b"); private IntArray existingScreens; private IntArray newScreens; private IntSparseArrayMap screenOccupancy; @Before public void initData() throws Exception { existingScreens = new IntArray(); screenOccupancy = new IntSparseArrayMap<>(); newScreens = new IntArray(); idp.numColumns = 5; idp.numRows = 5; } 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(appState, bgDataModel, existingScreens, newScreens, 1, 1); assertEquals(2, spaceFound[0]); assertTrue(screenOccupancy.get(spaceFound[0]) .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); // Find a larger space spaceFound = newTask() .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3); assertEquals(2, spaceFound[0]); assertTrue(screenOccupancy.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 = existingScreens.clone(); int[] spaceFound = newTask() .findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3); assertFalse(oldScreens.contains(spaceFound[0])); assertTrue(newScreens.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(executeTaskForTest(newTask(info)).isEmpty()); } @Test public void testAddItem_some_items_added() throws Exception { 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)); 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 { GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows); occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true); for (Rect r : holes) { occupancy.markCells(r, false); } existingScreens.add(screenId); screenOccupancy.append(screenId, occupancy); ExecutorService executor = Executors.newSingleThreadExecutor(); for (int x = 0; x < idp.numColumns; x++) { for (int y = 0; y < idp.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(targetContext, info, false); executor.execute(() -> { ContentWriter writer = new ContentWriter(targetContext); info.writeToValues(writer); writer.put(Favorites._ID, info.id); targetContext.getContentResolver().insert(Favorites.CONTENT_URI, writer.getValues(targetContext)); }); } } executor.submit(() -> null).get(); executor.shutdown(); return startId; } }