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