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