Home | History | Annotate | Download | only in model
      1 package com.android.launcher3.model;
      2 
      3 import android.content.ContentValues;
      4 import android.content.Intent;
      5 import android.database.Cursor;
      6 import android.graphics.Point;
      7 import android.test.ProviderTestCase2;
      8 import android.test.suitebuilder.annotation.MediumTest;
      9 
     10 import com.android.launcher3.InvariantDeviceProfile;
     11 import com.android.launcher3.LauncherModel;
     12 import com.android.launcher3.LauncherProvider;
     13 import com.android.launcher3.LauncherSettings;
     14 import com.android.launcher3.config.FeatureFlags;
     15 import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
     16 import com.android.launcher3.util.TestLauncherProvider;
     17 
     18 import java.util.ArrayList;
     19 import java.util.HashSet;
     20 import java.util.LinkedList;
     21 
     22 /**
     23  * Unit tests for {@link GridSizeMigrationTask}
     24  */
     25 @MediumTest
     26 public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherProvider> {
     27 
     28     private static final long DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
     29     private static final long HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;
     30 
     31     private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
     32     private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
     33 
     34     private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
     35     private static final String VALID_INTENT =
     36             new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0);
     37 
     38     private HashSet<String> mValidPackages;
     39     private InvariantDeviceProfile mIdp;
     40 
     41     public GridSizeMigrationTaskTest() {
     42         super(TestLauncherProvider.class, LauncherProvider.AUTHORITY);
     43     }
     44 
     45     @Override
     46     protected void setUp() throws Exception {
     47         super.setUp();
     48         mValidPackages = new HashSet<>();
     49         mValidPackages.add(TEST_PACKAGE);
     50 
     51         mIdp = new InvariantDeviceProfile();
     52     }
     53 
     54     public void testHotseatMigration_apps_dropped() throws Exception {
     55         long[] hotseatItems = {
     56                 addItem(APPLICATION, 0, HOTSEAT, 0, 0),
     57                 addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
     58                 -1,
     59                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
     60                 addItem(APPLICATION, 4, HOTSEAT, 0, 0),
     61         };
     62 
     63         mIdp.numHotseatIcons = 3;
     64         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
     65                 .migrateHotseat();
     66         if (FeatureFlags.NO_ALL_APPS_ICON) {
     67             // First item is dropped as it has the least weight.
     68             verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
     69         } else {
     70             // First & last items are dropped as they have the least weight.
     71             verifyHotseat(hotseatItems[1], -1, hotseatItems[3]);
     72         }
     73     }
     74 
     75     public void testHotseatMigration_shortcuts_dropped() throws Exception {
     76         long[] hotseatItems = {
     77                 addItem(APPLICATION, 0, HOTSEAT, 0, 0),
     78                 addItem(30, 1, HOTSEAT, 0, 0),
     79                 -1,
     80                 addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
     81                 addItem(10, 4, HOTSEAT, 0, 0),
     82         };
     83 
     84         mIdp.numHotseatIcons = 3;
     85         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages, 5, 3)
     86                 .migrateHotseat();
     87         if (FeatureFlags.NO_ALL_APPS_ICON) {
     88             // First item is dropped as it has the least weight.
     89             verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
     90         } else {
     91             // First & third items are dropped as they have the least weight.
     92             verifyHotseat(hotseatItems[1], -1, hotseatItems[4]);
     93         }
     94     }
     95 
     96     private void verifyHotseat(long... sortedIds) {
     97         int screenId = 0;
     98         int total = 0;
     99 
    100         for (long id : sortedIds) {
    101             Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
    102                     new String[]{LauncherSettings.Favorites._ID},
    103                     "container=-101 and screen=" + screenId, null, null, null);
    104 
    105             if (id == -1) {
    106                 assertEquals(0, c.getCount());
    107             } else {
    108                 assertEquals(1, c.getCount());
    109                 c.moveToNext();
    110                 assertEquals(id, c.getLong(0));
    111                 total ++;
    112             }
    113             c.close();
    114 
    115             screenId++;
    116         }
    117 
    118         // Verify that not other entry exist in the DB.
    119         Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
    120                 new String[]{LauncherSettings.Favorites._ID},
    121                 "container=-101", null, null, null);
    122         assertEquals(total, c.getCount());
    123         c.close();
    124     }
    125 
    126     public void testWorkspace_empty_row_column_removed() throws Exception {
    127         long[][][] ids = createGrid(new int[][][]{{
    128                 {  0,  0, -1,  1},
    129                 {  3,  1, -1,  4},
    130                 { -1, -1, -1, -1},
    131                 {  5,  2, -1,  6},
    132         }});
    133 
    134         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    135                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
    136 
    137         // Column 2 and row 2 got removed.
    138         verifyWorkspace(new long[][][] {{
    139                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
    140                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
    141                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
    142         }});
    143     }
    144 
    145     public void testWorkspace_new_screen_created() throws Exception {
    146         long[][][] ids = createGrid(new int[][][]{{
    147                 {  0,  0,  0,  1},
    148                 {  3,  1,  0,  4},
    149                 { -1, -1, -1, -1},
    150                 {  5,  2, -1,  6},
    151         }});
    152 
    153         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    154                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
    155 
    156         // Items in the second column get moved to new screen
    157         verifyWorkspace(new long[][][] {{
    158                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
    159                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
    160                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
    161         }, {
    162                 {ids[0][0][2], ids[0][1][2], -1},
    163         }});
    164     }
    165 
    166     public void testWorkspace_items_merged_in_next_screen() throws Exception {
    167         long[][][] ids = createGrid(new int[][][]{{
    168                 {  0,  0,  0,  1},
    169                 {  3,  1,  0,  4},
    170                 { -1, -1, -1, -1},
    171                 {  5,  2, -1,  6},
    172         },{
    173                 {  0,  0, -1,  1},
    174                 {  3,  1, -1,  4},
    175         }});
    176 
    177         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    178                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
    179 
    180         // Items in the second column of the first screen should get placed on the 3rd
    181         // row of the second screen
    182         verifyWorkspace(new long[][][] {{
    183                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
    184                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
    185                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
    186         }, {
    187                 {ids[1][0][0], ids[1][0][1], ids[1][0][3]},
    188                 {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
    189                 {ids[0][0][2], ids[0][1][2], -1},
    190         }});
    191     }
    192 
    193     public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
    194         // First screen has 2 items that need to be moved, but second screen has only one
    195         // empty space after migration (top-left corner)
    196         long[][][] ids = createGrid(new int[][][]{{
    197                 {  0,  0,  0,  1},
    198                 {  3,  1,  0,  4},
    199                 { -1, -1, -1, -1},
    200                 {  5,  2, -1,  6},
    201         },{
    202                 { -1,  0, -1,  1},
    203                 {  3,  1, -1,  4},
    204                 { -1, -1, -1, -1},
    205                 {  5,  2, -1,  6},
    206         }});
    207 
    208         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    209                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
    210 
    211         // Items in the second column of the first screen should get placed on a new screen.
    212         verifyWorkspace(new long[][][] {{
    213                 {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
    214                 {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
    215                 {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
    216         }, {
    217                 {          -1, ids[1][0][1], ids[1][0][3]},
    218                 {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
    219                 {ids[1][3][0], ids[1][3][1], ids[1][3][3]},
    220         }, {
    221                 {ids[0][0][2], ids[0][1][2], -1},
    222         }});
    223     }
    224 
    225     public void testWorkspace_first_row_blocked() throws Exception {
    226         // The first screen has one item on the 4th column which needs moving, as the first row
    227         // will be kept empty.
    228         long[][][] ids = createGrid(new int[][][]{{
    229                 { -1, -1, -1, -1},
    230                 {  3,  1,  7,  0},
    231                 {  8,  7,  7, -1},
    232                 {  5,  2,  7, -1},
    233         }}, 0);
    234 
    235         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    236                 new Point(4, 4), new Point(3, 4)).migrateWorkspace();
    237 
    238         // Items in the second column of the first screen should get placed on a new screen.
    239         verifyWorkspace(new long[][][] {{
    240                 {          -1,           -1,           -1},
    241                 {ids[0][1][0], ids[0][1][1], ids[0][1][2]},
    242                 {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
    243                 {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
    244         }, {
    245                 {ids[0][1][3]},
    246         }});
    247     }
    248 
    249     public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
    250         // Items will get moved to the next screen to keep the first screen empty.
    251         long[][][] ids = createGrid(new int[][][]{{
    252                 { -1, -1, -1, -1},
    253                 {  0,  1,  0,  0},
    254                 {  8,  7,  7, -1},
    255                 {  5,  6,  7, -1},
    256         }}, 0);
    257 
    258         new GridSizeMigrationTask(getMockContext(), mIdp, mValidPackages,
    259                 new Point(4, 4), new Point(3, 3)).migrateWorkspace();
    260 
    261         // Items in the second column of the first screen should get placed on a new screen.
    262         verifyWorkspace(new long[][][] {{
    263                 {          -1,           -1,           -1},
    264                 {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
    265                 {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
    266         }, {
    267                 {ids[0][1][1], ids[0][1][0], ids[0][1][2]},
    268                 {ids[0][1][3]},
    269         }});
    270     }
    271 
    272     private long[][][] createGrid(int[][][] typeArray) throws Exception {
    273         return createGrid(typeArray, 1);
    274     }
    275 
    276     /**
    277      * Initializes the DB with dummy elements to represent the provided grid structure.
    278      * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
    279      *                  type definitions. The first dimension represents the screens and the next
    280      *                  two represent the workspace grid.
    281      * @return the same grid representation where each entry is the corresponding item id.
    282      */
    283     private long[][][] createGrid(int[][][] typeArray, long startScreen) throws Exception {
    284         LauncherSettings.Settings.call(getMockContentResolver(),
    285                 LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
    286         long[][][] ids = new long[typeArray.length][][];
    287 
    288         for (int i = 0; i < typeArray.length; i++) {
    289             // Add screen to DB
    290             long screenId = startScreen + i;
    291 
    292             // Keep the screen id counter up to date
    293             LauncherSettings.Settings.call(getMockContentResolver(),
    294                     LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
    295 
    296             ContentValues v = new ContentValues();
    297             v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
    298             v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
    299             getMockContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
    300 
    301             ids[i] = new long[typeArray[i].length][];
    302             for (int y = 0; y < typeArray[i].length; y++) {
    303                 ids[i][y] = new long[typeArray[i][y].length];
    304                 for (int x = 0; x < typeArray[i][y].length; x++) {
    305                     if (typeArray[i][y][x] < 0) {
    306                         // Empty cell
    307                         ids[i][y][x] = -1;
    308                     } else {
    309                         ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
    310                     }
    311                 }
    312             }
    313         }
    314         return ids;
    315     }
    316 
    317     /**
    318      * Verifies that the workspace items are arranged in the provided order.
    319      * @param ids A 3d array where the first dimension represents the screen, and the rest two
    320      *            represent the workspace grid.
    321      */
    322     private void verifyWorkspace(long[][][] ids) {
    323         ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(getMockContext());
    324         assertEquals(ids.length, allScreens.size());
    325         int total = 0;
    326 
    327         for (int i = 0; i < ids.length; i++) {
    328             long screenId = allScreens.get(i);
    329             for (int y = 0; y < ids[i].length; y++) {
    330                 for (int x = 0; x < ids[i][y].length; x++) {
    331                     long id = ids[i][y][x];
    332 
    333                     Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
    334                             new String[]{LauncherSettings.Favorites._ID},
    335                             "container=-100 and screen=" + screenId +
    336                                     " and cellX=" + x + " and cellY=" + y, null, null, null);
    337                     if (id == -1) {
    338                         assertEquals(0, c.getCount());
    339                     } else {
    340                         assertEquals(1, c.getCount());
    341                         c.moveToNext();
    342                         assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
    343                                 id, c.getLong(0));
    344                         total++;
    345                     }
    346                     c.close();
    347                 }
    348             }
    349         }
    350 
    351         // Verify that not other entry exist in the DB.
    352         Cursor c = getMockContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
    353                 new String[]{LauncherSettings.Favorites._ID},
    354                 "container=-100", null, null, null);
    355         assertEquals(total, c.getCount());
    356         c.close();
    357     }
    358 
    359     /**
    360      * Adds a dummy item in the DB.
    361      * @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
    362      *             folder (where the type represents the number of items in the folder).
    363      */
    364     private long addItem(int type, long screen, long container, int x, int y) throws Exception {
    365         long id = LauncherSettings.Settings.call(getMockContentResolver(),
    366                 LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
    367                 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
    368 
    369         ContentValues values = new ContentValues();
    370         values.put(LauncherSettings.Favorites._ID, id);
    371         values.put(LauncherSettings.Favorites.CONTAINER, container);
    372         values.put(LauncherSettings.Favorites.SCREEN, screen);
    373         values.put(LauncherSettings.Favorites.CELLX, x);
    374         values.put(LauncherSettings.Favorites.CELLY, y);
    375         values.put(LauncherSettings.Favorites.SPANX, 1);
    376         values.put(LauncherSettings.Favorites.SPANY, 1);
    377 
    378         if (type == APPLICATION || type == SHORTCUT) {
    379             values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
    380             values.put(LauncherSettings.Favorites.INTENT, VALID_INTENT);
    381         } else {
    382             values.put(LauncherSettings.Favorites.ITEM_TYPE,
    383                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
    384             // Add folder items.
    385             for (int i = 0; i < type; i++) {
    386                 addItem(APPLICATION, 0, id, 0, 0);
    387             }
    388         }
    389 
    390         getMockContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
    391         return id;
    392     }
    393 
    394     public void testMultiStepMigration_small_to_large() throws Exception {
    395         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
    396         verifier.migrate(new Point(3, 3), new Point(5, 5));
    397         verifier.assertCompleted();
    398     }
    399 
    400     public void testMultiStepMigration_large_to_small() throws Exception {
    401         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
    402                 5, 5, 4, 4,
    403                 4, 4, 3, 4
    404         );
    405         verifier.migrate(new Point(5, 5), new Point(3, 4));
    406         verifier.assertCompleted();
    407     }
    408 
    409     public void testMultiStepMigration_zig_zag() throws Exception {
    410         MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
    411                 5, 7, 4, 7,
    412                 4, 7, 3, 7
    413         );
    414         verifier.migrate(new Point(5, 5), new Point(3, 7));
    415         verifier.assertCompleted();
    416     }
    417 
    418     private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
    419 
    420         private final LinkedList<Point> mPoints;
    421 
    422         public MultiStepMigrationTaskVerifier(int... points) {
    423             super(null, null);
    424 
    425             mPoints = new LinkedList<>();
    426             for (int i = 0; i < points.length; i += 2) {
    427                 mPoints.add(new Point(points[i], points[i + 1]));
    428             }
    429         }
    430 
    431         @Override
    432         protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
    433             assertEquals(sourceSize, mPoints.poll());
    434             assertEquals(nextSize, mPoints.poll());
    435             return false;
    436         }
    437 
    438         public void assertCompleted() {
    439             assertTrue(mPoints.isEmpty());
    440         }
    441     }
    442 }
    443