1 package com.android.launcher3.model; 2 3 import static org.junit.Assert.assertEquals; 4 import static org.junit.Assert.assertFalse; 5 import static org.junit.Assert.assertTrue; 6 import static org.mockito.Mockito.any; 7 import static org.mockito.Mockito.mock; 8 import static org.mockito.Mockito.verify; 9 10 import android.content.ComponentName; 11 import android.content.Context; 12 import android.content.Intent; 13 import android.graphics.Rect; 14 import android.util.Pair; 15 16 import androidx.test.ext.junit.runners.AndroidJUnit4; 17 import androidx.test.filters.SmallTest; 18 19 import com.android.launcher3.InvariantDeviceProfile; 20 import com.android.launcher3.LauncherAppState; 21 import com.android.launcher3.LauncherSettings; 22 import com.android.launcher3.LauncherSettings.Favorites; 23 import com.android.launcher3.model.BgDataModel.Callbacks; 24 import com.android.launcher3.model.data.ItemInfo; 25 import com.android.launcher3.model.data.WorkspaceItemInfo; 26 import com.android.launcher3.util.ContentWriter; 27 import com.android.launcher3.util.Executors; 28 import com.android.launcher3.util.GridOccupancy; 29 import com.android.launcher3.util.IntArray; 30 import com.android.launcher3.util.IntSparseArrayMap; 31 import com.android.launcher3.util.LauncherModelHelper; 32 33 import org.junit.After; 34 import org.junit.Before; 35 import org.junit.Test; 36 import org.junit.runner.RunWith; 37 import org.mockito.ArgumentCaptor; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Tests for {@link AddWorkspaceItemsTask} 44 */ 45 @SmallTest 46 @RunWith(AndroidJUnit4.class) 47 public class AddWorkspaceItemsTaskTest { 48 49 private final ComponentName mComponent1 = new ComponentName("a", "b"); 50 private final ComponentName mComponent2 = new ComponentName("b", "b"); 51 52 private Context mTargetContext; 53 private InvariantDeviceProfile mIdp; 54 private LauncherAppState mAppState; 55 private LauncherModelHelper mModelHelper; 56 57 private IntArray mExistingScreens; 58 private IntArray mNewScreens; 59 private IntSparseArrayMap<GridOccupancy> mScreenOccupancy; 60 61 @Before setup()62 public void setup() { 63 mModelHelper = new LauncherModelHelper(); 64 mTargetContext = mModelHelper.sandboxContext; 65 mIdp = InvariantDeviceProfile.INSTANCE.get(mTargetContext); 66 mIdp.numColumns = mIdp.numRows = 5; 67 mAppState = LauncherAppState.getInstance(mTargetContext); 68 69 mExistingScreens = new IntArray(); 70 mScreenOccupancy = new IntSparseArrayMap<>(); 71 mNewScreens = new IntArray(); 72 } 73 74 @After tearDown()75 public void tearDown() { 76 mModelHelper.destroy(); 77 } 78 newTask(ItemInfo... items)79 private AddWorkspaceItemsTask newTask(ItemInfo... items) { 80 List<Pair<ItemInfo, Object>> list = new ArrayList<>(); 81 for (ItemInfo item : items) { 82 list.add(Pair.create(item, null)); 83 } 84 return new AddWorkspaceItemsTask(list); 85 } 86 87 @Test testFindSpaceForItem_prefers_second()88 public void testFindSpaceForItem_prefers_second() throws Exception { 89 // First screen has only one hole of size 1 90 int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); 91 92 // Second screen has 2 holes of sizes 3x2 and 2x3 93 setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); 94 95 int[] spaceFound = newTask().findSpaceForItem( 96 mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 1, 1); 97 assertEquals(1, spaceFound[0]); 98 assertTrue(mScreenOccupancy.get(spaceFound[0]) 99 .isRegionVacant(spaceFound[1], spaceFound[2], 1, 1)); 100 101 // Find a larger space 102 spaceFound = newTask().findSpaceForItem( 103 mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 2, 3); 104 assertEquals(2, spaceFound[0]); 105 assertTrue(mScreenOccupancy.get(spaceFound[0]) 106 .isRegionVacant(spaceFound[1], spaceFound[2], 2, 3)); 107 } 108 109 @Test testFindSpaceForItem_adds_new_screen()110 public void testFindSpaceForItem_adds_new_screen() throws Exception { 111 // First screen has 2 holes of sizes 3x2 and 2x3 112 setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5)); 113 114 IntArray oldScreens = mExistingScreens.clone(); 115 int[] spaceFound = newTask().findSpaceForItem( 116 mAppState, mModelHelper.getBgDataModel(), mExistingScreens, mNewScreens, 3, 3); 117 assertFalse(oldScreens.contains(spaceFound[0])); 118 assertTrue(mNewScreens.contains(spaceFound[0])); 119 } 120 121 @Test testAddItem_existing_item_ignored()122 public void testAddItem_existing_item_ignored() throws Exception { 123 WorkspaceItemInfo info = new WorkspaceItemInfo(); 124 info.intent = new Intent().setComponent(mComponent1); 125 126 // Setup a screen with a hole 127 setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); 128 129 // Nothing was added 130 assertTrue(mModelHelper.executeTaskForTest(newTask(info)).isEmpty()); 131 } 132 133 @Test testAddItem_some_items_added()134 public void testAddItem_some_items_added() throws Exception { 135 Callbacks callbacks = mock(Callbacks.class); 136 Executors.MAIN_EXECUTOR.submit(() -> mModelHelper.getModel().addCallbacks(callbacks)).get(); 137 138 WorkspaceItemInfo info = new WorkspaceItemInfo(); 139 info.intent = new Intent().setComponent(mComponent1); 140 141 WorkspaceItemInfo info2 = new WorkspaceItemInfo(); 142 info2.intent = new Intent().setComponent(mComponent2); 143 144 // Setup a screen with a hole 145 setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3)); 146 147 mModelHelper.executeTaskForTest(newTask(info, info2)).get(0).run(); 148 ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class); 149 ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class); 150 151 // only info2 should be added because info was already added to the workspace 152 // in setupWorkspaceWithHoles() 153 verify(callbacks).bindAppsAdded(any(IntArray.class), notAnimated.capture(), 154 animated.capture()); 155 assertTrue(notAnimated.getValue().isEmpty()); 156 157 assertEquals(1, animated.getValue().size()); 158 assertTrue(animated.getValue().contains(info2)); 159 } 160 setupWorkspaceWithHoles(int startId, int screenId, Rect... holes)161 private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception { 162 return mModelHelper.executeSimpleTask( 163 model -> writeWorkspaceWithHoles(model, startId, screenId, holes)); 164 } 165 writeWorkspaceWithHoles( BgDataModel bgDataModel, int startId, int screenId, Rect... holes)166 private int writeWorkspaceWithHoles( 167 BgDataModel bgDataModel, int startId, int screenId, Rect... holes) { 168 GridOccupancy occupancy = new GridOccupancy(mIdp.numColumns, mIdp.numRows); 169 occupancy.markCells(0, 0, mIdp.numColumns, mIdp.numRows, true); 170 for (Rect r : holes) { 171 occupancy.markCells(r, false); 172 } 173 174 mExistingScreens.add(screenId); 175 mScreenOccupancy.append(screenId, occupancy); 176 177 for (int x = 0; x < mIdp.numColumns; x++) { 178 for (int y = 0; y < mIdp.numRows; y++) { 179 if (!occupancy.cells[x][y]) { 180 continue; 181 } 182 183 WorkspaceItemInfo info = new WorkspaceItemInfo(); 184 info.intent = new Intent().setComponent(mComponent1); 185 info.id = startId++; 186 info.screenId = screenId; 187 info.cellX = x; 188 info.cellY = y; 189 info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP; 190 bgDataModel.addItem(mTargetContext, info, false); 191 192 ContentWriter writer = new ContentWriter(mTargetContext); 193 info.writeToValues(writer); 194 writer.put(Favorites._ID, info.id); 195 mTargetContext.getContentResolver().insert(Favorites.CONTENT_URI, 196 writer.getValues(mTargetContext)); 197 } 198 } 199 return startId; 200 } 201 } 202