1 package com.android.launcher3.model;
2 
3 import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds;
4 
5 import static org.junit.Assert.assertEquals;
6 import static org.junit.Assert.assertTrue;
7 
8 import android.database.Cursor;
9 import android.graphics.Point;
10 
11 import com.android.launcher3.InvariantDeviceProfile;
12 import com.android.launcher3.LauncherSettings;
13 import com.android.launcher3.config.FeatureFlags;
14 import com.android.launcher3.config.FlagOverrideRule;
15 import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
16 import com.android.launcher3.util.IntArray;
17 
18 import org.junit.Before;
19 import org.junit.Rule;
20 import org.junit.Test;
21 import org.junit.runner.RunWith;
22 import org.robolectric.RobolectricTestRunner;
23 
24 import java.util.HashSet;
25 import java.util.LinkedList;
26 
27 /**
28  * Unit tests for {@link GridSizeMigrationTask}
29  */
30 @RunWith(RobolectricTestRunner.class)
31 public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {
32 
33     @Rule
34     public final FlagOverrideRule flags = new FlagOverrideRule();
35 
36     private HashSet<String> mValidPackages;
37     private InvariantDeviceProfile mIdp;
38 
39     @Before
setUp()40     public void setUp() {
41         mValidPackages = new HashSet<>();
42         mValidPackages.add(TEST_PACKAGE);
43         mIdp = new InvariantDeviceProfile();
44     }
45 
46     @Test
testHotseatMigration_apps_dropped()47     public void testHotseatMigration_apps_dropped() throws Exception {
48         int[] hotseatItems = {
49                 addItem(APP_ICON, 0, HOTSEAT, 0, 0),
50                 addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
51                 -1,
52                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
53                 addItem(APP_ICON, 4, HOTSEAT, 0, 0),
54         };
55 
56         mIdp.numHotseatIcons = 3;
57         new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
58                 .migrateHotseat();
59         // First item is dropped as it has the least weight.
60         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
61     }
62 
63     @Test
testHotseatMigration_shortcuts_dropped()64     public void testHotseatMigration_shortcuts_dropped() throws Exception {
65         int[] hotseatItems = {
66                 addItem(APP_ICON, 0, HOTSEAT, 0, 0),
67                 addItem(30, 1, HOTSEAT, 0, 0),
68                 -1,
69                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
70                 addItem(10, 4, HOTSEAT, 0, 0),
71         };
72 
73         mIdp.numHotseatIcons = 3;
74         new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
75                 .migrateHotseat();
76         // First item is dropped as it has the least weight.
77         verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
78     }
79 
verifyHotseat(int... sortedIds)80     private void verifyHotseat(int... sortedIds) {
81         int screenId = 0;
82         int total = 0;
83 
84         for (int id : sortedIds) {
85             Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
86                     new String[]{LauncherSettings.Favorites._ID},
87                     "container=-101 and screen=" + screenId, null, null, null);
88 
89             if (id == -1) {
90                 assertEquals(0, c.getCount());
91             } else {
92                 assertEquals(1, c.getCount());
93                 c.moveToNext();
94                 assertEquals(id, c.getLong(0));
95                 total ++;
96             }
97             c.close();
98 
99             screenId++;
100         }
101 
102         // Verify that not other entry exist in the DB.
103         Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
104                 new String[]{LauncherSettings.Favorites._ID},
105                 "container=-101", null, null, null);
106         assertEquals(total, c.getCount());
107         c.close();
108     }
109 
110     @Test
testWorkspace_empty_row_column_removed()111     public void testWorkspace_empty_row_column_removed() throws Exception {
112         int[][][] ids = createGrid(new int[][][]{{
113                 {  0,  0, -1,  1},
114                 {  3,  1, -1,  4},
115                 { -1, -1, -1, -1},
116                 {  5,  2, -1,  6},
117         }});
118 
119         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
120                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
121 
122         // Column 2 and row 2 got removed.
123         verifyWorkspace(new int[][][] {{
124                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
125                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
126                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
127         }});
128     }
129 
130     @Test
testWorkspace_new_screen_created()131     public void testWorkspace_new_screen_created() throws Exception {
132         int[][][] ids = createGrid(new int[][][]{{
133                 {  0,  0,  0,  1},
134                 {  3,  1,  0,  4},
135                 { -1, -1, -1, -1},
136                 {  5,  2, -1,  6},
137         }});
138 
139         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
140                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
141 
142         // Items in the second column get moved to new screen
143         verifyWorkspace(new int[][][] {{
144                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
145                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
146                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
147         }, {
148                 {ids[0][0][2], ids[0][1][2], -1},
149         }});
150     }
151 
152     @Test
testWorkspace_items_merged_in_next_screen()153     public void testWorkspace_items_merged_in_next_screen() throws Exception {
154         int[][][] ids = createGrid(new int[][][]{{
155                 {  0,  0,  0,  1},
156                 {  3,  1,  0,  4},
157                 { -1, -1, -1, -1},
158                 {  5,  2, -1,  6},
159         },{
160                 {  0,  0, -1,  1},
161                 {  3,  1, -1,  4},
162         }});
163 
164         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
165                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
166 
167         // Items in the second column of the first screen should get placed on the 3rd
168         // row of the second screen
169         verifyWorkspace(new int[][][] {{
170                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
171                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
172                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
173         }, {
174                 {ids[1][0][0], ids[1][0][1], ids[1][0][3]},
175                 {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
176                 {ids[0][0][2], ids[0][1][2], -1},
177         }});
178     }
179 
180     @Test
testWorkspace_items_not_merged_in_next_screen()181     public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
182         // First screen has 2 items that need to be moved, but second screen has only one
183         // empty space after migration (top-left corner)
184         int[][][] ids = createGrid(new int[][][]{{
185                 {  0,  0,  0,  1},
186                 {  3,  1,  0,  4},
187                 { -1, -1, -1, -1},
188                 {  5,  2, -1,  6},
189         },{
190                 { -1,  0, -1,  1},
191                 {  3,  1, -1,  4},
192                 { -1, -1, -1, -1},
193                 {  5,  2, -1,  6},
194         }});
195 
196         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
197                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
198 
199         // Items in the second column of the first screen should get placed on a new screen.
200         verifyWorkspace(new int[][][] {{
201                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
202                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
203                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
204         }, {
205                 {          -1, ids[1][0][1], ids[1][0][3]},
206                 {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
207                 {ids[1][3][0], ids[1][3][1], ids[1][3][3]},
208         }, {
209                 {ids[0][0][2], ids[0][1][2], -1},
210         }});
211     }
212 
213     @Test
testWorkspace_first_row_blocked()214     public void testWorkspace_first_row_blocked() throws Exception {
215         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
216             return;
217         }
218         // The first screen has one item on the 4th column which needs moving, as the first row
219         // will be kept empty.
220         int[][][] ids = createGrid(new int[][][]{{
221                 { -1, -1, -1, -1},
222                 {  3,  1,  7,  0},
223                 {  8,  7,  7, -1},
224                 {  5,  2,  7, -1},
225         }}, 0);
226 
227         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
228                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
229 
230         // Items in the second column of the first screen should get placed on a new screen.
231         verifyWorkspace(new int[][][] {{
232                 {          -1,           -1,           -1},
233                 {ids[0][1][0], ids[0][1][1], ids[0][1][2]},
234                 {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
235                 {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
236         }, {
237                 {ids[0][1][3]},
238         }});
239     }
240 
241     @Test
testWorkspace_items_moved_to_empty_first_row()242     public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
243         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
244             return;
245         }
246         // Items will get moved to the next screen to keep the first screen empty.
247         int[][][] ids = createGrid(new int[][][]{{
248                 { -1, -1, -1, -1},
249                 {  0,  1,  0,  0},
250                 {  8,  7,  7, -1},
251                 {  5,  6,  7, -1},
252         }}, 0);
253 
254         new GridSizeMigrationTask(mContext, mDb, mValidPackages,
255                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
256 
257         // Items in the second column of the first screen should get placed on a new screen.
258         verifyWorkspace(new int[][][] {{
259                 {          -1,           -1,           -1},
260                 {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
261                 {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
262         }, {
263                 {ids[0][1][1], ids[0][1][0], ids[0][1][2]},
264                 {ids[0][1][3]},
265         }});
266     }
267 
268     /**
269      * Verifies that the workspace items are arranged in the provided order.
270      * @param ids A 3d array where the first dimension represents the screen, and the rest two
271      *            represent the workspace grid.
272      */
verifyWorkspace(int[][][] ids)273     private void verifyWorkspace(int[][][] ids) {
274         IntArray allScreens = getWorkspaceScreenIds(mDb);
275         assertEquals(ids.length, allScreens.size());
276         int total = 0;
277 
278         for (int i = 0; i < ids.length; i++) {
279             int screenId = allScreens.get(i);
280             for (int y = 0; y < ids[i].length; y++) {
281                 for (int x = 0; x < ids[i][y].length; x++) {
282                     int id = ids[i][y][x];
283 
284                     Cursor c = mContext.getContentResolver().query(
285                             LauncherSettings.Favorites.CONTENT_URI,
286                             new String[]{LauncherSettings.Favorites._ID},
287                             "container=-100 and screen=" + screenId +
288                                     " and cellX=" + x + " and cellY=" + y, null, null, null);
289                     if (id == -1) {
290                         assertEquals(0, c.getCount());
291                     } else {
292                         assertEquals(1, c.getCount());
293                         c.moveToNext();
294                         assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
295                                 id, c.getLong(0));
296                         total++;
297                     }
298                     c.close();
299                 }
300             }
301         }
302 
303         // Verify that not other entry exist in the DB.
304         Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
305                 new String[]{LauncherSettings.Favorites._ID},
306                 "container=-100", null, null, null);
307         assertEquals(total, c.getCount());
308         c.close();
309     }
310 
311     @Test
testMultiStepMigration_small_to_large()312     public void testMultiStepMigration_small_to_large() throws Exception {
313         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
314         verifier.migrate(new Point(3, 3), new Point(5, 5));
315         verifier.assertCompleted();
316     }
317 
318     @Test
testMultiStepMigration_large_to_small()319     public void testMultiStepMigration_large_to_small() throws Exception {
320         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
321                 5, 5, 4, 4,
322                 4, 4, 3, 4
323         );
324         verifier.migrate(new Point(5, 5), new Point(3, 4));
325         verifier.assertCompleted();
326     }
327 
328     @Test
testMultiStepMigration_zig_zag()329     public void testMultiStepMigration_zig_zag() throws Exception {
330         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
331                 5, 7, 4, 7,
332                 4, 7, 3, 7
333         );
334         verifier.migrate(new Point(5, 5), new Point(3, 7));
335         verifier.assertCompleted();
336     }
337 
338     private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
339 
340         private final LinkedList<Point> mPoints;
341 
MultiStepMigrationTaskVerifier(int... points)342         public MultiStepMigrationTaskVerifier(int... points) {
343             super(null, null, null);
344 
345             mPoints = new LinkedList<>();
346             for (int i = 0; i < points.length; i += 2) {
347                 mPoints.add(new Point(points[i], points[i + 1]));
348             }
349         }
350 
351         @Override
runStepTask(Point sourceSize, Point nextSize)352         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
353             assertEquals(sourceSize, mPoints.poll());
354             assertEquals(nextSize, mPoints.poll());
355             return false;
356         }
357 
assertCompleted()358         public void assertCompleted() {
359             assertTrue(mPoints.isEmpty());
360         }
361     }
362 }
363