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