Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 161a214e authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Adding support for backing up favorites table

Favorites table is copied as a separate table name during the first grid migration.
On subsequent migrations this backup table is used if it exists, otherwise new
backup is created. The backup table is also removed if there is any insert or
delete operation on the db (outside of the migration operation itself).

Bug: 111850268
Bug: 121048571
Change-Id: I6f02f4a355c369ee99d89430971be258f7516f6e
parent efb8b836
Loading
Loading
Loading
Loading
+121 −0
Original line number Diff line number Diff line
package com.android.launcher3.model;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.sqlite.SQLiteDatabase;

import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.util.TestLauncherProvider;

import org.junit.Before;
import org.robolectric.Robolectric;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowContentResolver;
import org.robolectric.shadows.ShadowLog;

public abstract class BaseGridChangesTestCase {


    public static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
    public static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;

    public static final int APP_ICON = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
    public static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
    public static final int NO__ICON = -1;

    public static final String TEST_PACKAGE = "com.android.launcher3.validpackage";

    public Context mContext;
    public TestLauncherProvider mProvider;
    public SQLiteDatabase mDb;

    @Before
    public void setUpBaseCase() {
        ShadowLog.stream = System.out;

        mContext = RuntimeEnvironment.application;
        mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
        mDb = mProvider.getDb();
    }

    /**
     * Adds a dummy item in the DB.
     * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
     *             folder (where the type represents the number of items in the folder).
     */
    public int addItem(int type, int screen, int container, int x, int y) {
        int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
                LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                .getInt(LauncherSettings.Settings.EXTRA_VALUE);

        ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites._ID, id);
        values.put(LauncherSettings.Favorites.CONTAINER, container);
        values.put(LauncherSettings.Favorites.SCREEN, screen);
        values.put(LauncherSettings.Favorites.CELLX, x);
        values.put(LauncherSettings.Favorites.CELLY, y);
        values.put(LauncherSettings.Favorites.SPANX, 1);
        values.put(LauncherSettings.Favorites.SPANY, 1);

        if (type == APP_ICON || type == SHORTCUT) {
            values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
            values.put(LauncherSettings.Favorites.INTENT,
                    new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
        } else {
            values.put(LauncherSettings.Favorites.ITEM_TYPE,
                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
            // Add folder items.
            for (int i = 0; i < type; i++) {
                addItem(APP_ICON, 0, id, 0, 0);
            }
        }

        mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
        return id;
    }

    public int[][][] createGrid(int[][][] typeArray) {
        return createGrid(typeArray, 1);
    }

    /**
     * Initializes the DB with dummy elements to represent the provided grid structure.
     * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
     *                  type definitions. The first dimension represents the screens and the next
     *                  two represent the workspace grid.
     * @param startScreen First screen id from where the icons will be added.
     * @return the same grid representation where each entry is the corresponding item id.
     */
    public int[][][] createGrid(int[][][] typeArray, int startScreen) {
        LauncherSettings.Settings.call(mContext.getContentResolver(),
                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
        int[][][] ids = new int[typeArray.length][][];

        for (int i = 0; i < typeArray.length; i++) {
            // Add screen to DB
            int screenId = startScreen + i;

            // Keep the screen id counter up to date
            LauncherSettings.Settings.call(mContext.getContentResolver(),
                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);

            ids[i] = new int[typeArray[i].length][];
            for (int y = 0; y < typeArray[i].length; y++) {
                ids[i][y] = new int[typeArray[i][y].length];
                for (int x = 0; x < typeArray[i][y].length; x++) {
                    if (typeArray[i][y][x] < 0) {
                        // Empty cell
                        ids[i][y][x] = -1;
                    } else {
                        ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
                    }
                }
            }
        }

        return ids;
    }
}
+115 −0
Original line number Diff line number Diff line
package com.android.launcher3.model;


import static android.database.DatabaseUtils.queryNumEntries;

import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.content.ContentValues;
import android.graphics.Point;

import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

/**
 * Unit tests for {@link GridBackupTable}
 */
@RunWith(RobolectricTestRunner.class)
public class GridBackupTableTest extends BaseGridChangesTestCase {

    private static final int BACKUP_ITEM_COUNT = 12;

    @Before
    public void setupGridData() {
        createGrid(new int[][][]{{
                { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
                { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
                { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
                { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
        }});
        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
    }

    @Test
    public void backupTableCreated() {
        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 4, 4, 4);
        assertFalse(backupTable.backupOrRestoreAsNeeded());
        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);

        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));

        // One extra entry for properties
        assertEquals(BACKUP_ITEM_COUNT + 1, queryNumEntries(mDb, BACKUP_TABLE_NAME));
    }

    @Test
    public void backupTableRestored() {
        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);

        // Delete entries
        mDb.delete(TABLE_NAME, null, null);
        assertEquals(0, queryNumEntries(mDb, TABLE_NAME));

        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 3, 3, 3);
        assertTrue(backupTable.backupOrRestoreAsNeeded());

        // Items have been restored
        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));

        Point outSize = new Point();
        assertEquals(4, backupTable.getRestoreHotseatAndGridSize(outSize));
        assertEquals(4, outSize.x);
        assertEquals(4, outSize.y);
    }

    @Test
    public void backupTableRemovedOnAdd() {
        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);

        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));

        addItem(1, 2, DESKTOP, 1, 1);
        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
    }

    @Test
    public void backupTableRemovedOnDelete() {
        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);

        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));

        mContext.getContentResolver().delete(Favorites.CONTENT_URI, null, null);
        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
    }

    @Test
    public void backupTableRetainedOnUpdate() {
        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);

        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));

        ContentValues values = new ContentValues();
        values.put(Favorites.RANK, 4);
        // Something was updated
        assertTrue(mContext.getContentResolver()
                .update(Favorites.CONTENT_URI, values, null, null) > 0);

        // Backup table remains
        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
    }
}
+14 −113
Original line number Diff line number Diff line
@@ -5,29 +5,21 @@ import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScre
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Point;

import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FlagOverrideRule;
import com.android.launcher3.config.FlagOverrideRule.FlagOverride;
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.TestLauncherProvider;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.shadows.ShadowContentResolver;

import java.util.HashSet;
import java.util.LinkedList;
@@ -36,47 +28,33 @@ import java.util.LinkedList;
 * Unit tests for {@link GridSizeMigrationTask}
 */
@RunWith(RobolectricTestRunner.class)
public class GridSizeMigrationTaskTest {

    private static final int DESKTOP = LauncherSettings.Favorites.CONTAINER_DESKTOP;
    private static final int HOTSEAT = LauncherSettings.Favorites.CONTAINER_HOTSEAT;

    private static final int APPLICATION = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
    private static final int SHORTCUT = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;

    private static final String TEST_PACKAGE = "com.android.launcher3.validpackage";
public class GridSizeMigrationTaskTest extends BaseGridChangesTestCase {

    @Rule
    public final FlagOverrideRule flags = new FlagOverrideRule();

    private HashSet<String> mValidPackages;
    private InvariantDeviceProfile mIdp;
    private Context mContext;
    private TestLauncherProvider mProvider;

    @Before
    public void setUp() {
        mValidPackages = new HashSet<>();
        mValidPackages.add(TEST_PACKAGE);
        mIdp = new InvariantDeviceProfile();
        mContext = RuntimeEnvironment.application;

        mProvider = Robolectric.setupContentProvider(TestLauncherProvider.class);
        ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, mProvider);
    }

    @Test
    public void testHotseatMigration_apps_dropped() throws Exception {
        int[] hotseatItems = {
                addItem(APPLICATION, 0, HOTSEAT, 0, 0),
                addItem(APP_ICON, 0, HOTSEAT, 0, 0),
                addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
                -1,
                addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
                addItem(APPLICATION, 4, HOTSEAT, 0, 0),
                addItem(APP_ICON, 4, HOTSEAT, 0, 0),
        };

        mIdp.numHotseatIcons = 3;
        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
                .migrateHotseat();
        // First item is dropped as it has the least weight.
        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -85,7 +63,7 @@ public class GridSizeMigrationTaskTest {
    @Test
    public void testHotseatMigration_shortcuts_dropped() throws Exception {
        int[] hotseatItems = {
                addItem(APPLICATION, 0, HOTSEAT, 0, 0),
                addItem(APP_ICON, 0, HOTSEAT, 0, 0),
                addItem(30, 1, HOTSEAT, 0, 0),
                -1,
                addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
@@ -93,7 +71,7 @@ public class GridSizeMigrationTaskTest {
        };

        mIdp.numHotseatIcons = 3;
        new GridSizeMigrationTask(mContext, mIdp, mValidPackages, 5, 3)
        new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
                .migrateHotseat();
        // First item is dropped as it has the least weight.
        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@@ -138,7 +116,7 @@ public class GridSizeMigrationTaskTest {
                {  5,  2, -1,  6},
        }});

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 3)).migrateWorkspace();

        // Column 2 and row 2 got removed.
@@ -158,7 +136,7 @@ public class GridSizeMigrationTaskTest {
                {  5,  2, -1,  6},
        }});

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 3)).migrateWorkspace();

        // Items in the second column get moved to new screen
@@ -183,7 +161,7 @@ public class GridSizeMigrationTaskTest {
                {  3,  1, -1,  4},
        }});

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 3)).migrateWorkspace();

        // Items in the second column of the first screen should get placed on the 3rd
@@ -215,7 +193,7 @@ public class GridSizeMigrationTaskTest {
                {  5,  2, -1,  6},
        }});

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 3)).migrateWorkspace();

        // Items in the second column of the first screen should get placed on a new screen.
@@ -244,7 +222,7 @@ public class GridSizeMigrationTaskTest {
                {  5,  2,  7, -1},
        }}, 0);

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 4)).migrateWorkspace();

        // Items in the second column of the first screen should get placed on a new screen.
@@ -269,7 +247,7 @@ public class GridSizeMigrationTaskTest {
                {  5,  6,  7, -1},
        }}, 0);

        new GridSizeMigrationTask(mContext, mIdp, mValidPackages,
        new GridSizeMigrationTask(mContext, mDb, mValidPackages,
                new Point(4, 4), new Point(3, 3)).migrateWorkspace();

        // Items in the second column of the first screen should get placed on a new screen.
@@ -283,54 +261,13 @@ public class GridSizeMigrationTaskTest {
        }});
    }

    private int[][][] createGrid(int[][][] typeArray) throws Exception {
        return createGrid(typeArray, 1);
    }

    /**
     * Initializes the DB with dummy elements to represent the provided grid structure.
     * @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
     *                  type definitions. The first dimension represents the screens and the next
     *                  two represent the workspace grid.
     * @return the same grid representation where each entry is the corresponding item id.
     */
    private int[][][] createGrid(int[][][] typeArray, int startScreen) throws Exception {
        LauncherSettings.Settings.call(mContext.getContentResolver(),
                LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
        int[][][] ids = new int[typeArray.length][][];

        for (int i = 0; i < typeArray.length; i++) {
            // Add screen to DB
            int screenId = startScreen + i;

            // Keep the screen id counter up to date
            LauncherSettings.Settings.call(mContext.getContentResolver(),
                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);

            ids[i] = new int[typeArray[i].length][];
            for (int y = 0; y < typeArray[i].length; y++) {
                ids[i][y] = new int[typeArray[i][y].length];
                for (int x = 0; x < typeArray[i][y].length; x++) {
                    if (typeArray[i][y][x] < 0) {
                        // Empty cell
                        ids[i][y][x] = -1;
                    } else {
                        ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
                    }
                }
            }
        }

        return ids;
    }

    /**
     * Verifies that the workspace items are arranged in the provided order.
     * @param ids A 3d array where the first dimension represents the screen, and the rest two
     *            represent the workspace grid.
     */
    private void verifyWorkspace(int[][][] ids) {
        IntArray allScreens = getWorkspaceScreenIds(mContext);
        IntArray allScreens = getWorkspaceScreenIds(mDb);
        assertEquals(ids.length, allScreens.size());
        int total = 0;

@@ -367,42 +304,6 @@ public class GridSizeMigrationTaskTest {
        c.close();
    }

    /**
     * Adds a dummy item in the DB.
     * @param type {@link #APPLICATION} or {@link #SHORTCUT} or >= 2 for
     *             folder (where the type represents the number of items in the folder).
     */
    private int addItem(int type, int screen, int container, int x, int y) throws Exception {
        int id = LauncherSettings.Settings.call(mContext.getContentResolver(),
                LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                .getInt(LauncherSettings.Settings.EXTRA_VALUE);

        ContentValues values = new ContentValues();
        values.put(LauncherSettings.Favorites._ID, id);
        values.put(LauncherSettings.Favorites.CONTAINER, container);
        values.put(LauncherSettings.Favorites.SCREEN, screen);
        values.put(LauncherSettings.Favorites.CELLX, x);
        values.put(LauncherSettings.Favorites.CELLY, y);
        values.put(LauncherSettings.Favorites.SPANX, 1);
        values.put(LauncherSettings.Favorites.SPANY, 1);

        if (type == APPLICATION || type == SHORTCUT) {
            values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
            values.put(LauncherSettings.Favorites.INTENT,
                    new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
        } else {
            values.put(LauncherSettings.Favorites.ITEM_TYPE,
                    LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
            // Add folder items.
            for (int i = 0; i < type; i++) {
                addItem(APPLICATION, 0, id, 0, 0);
            }
        }

        mContext.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
        return id;
    }

    @Test
    public void testMultiStepMigration_small_to_large() throws Exception {
        MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
@@ -435,7 +336,7 @@ public class GridSizeMigrationTaskTest {
        private final LinkedList<Point> mPoints;

        public MultiStepMigrationTaskVerifier(int... points) {
            super(null, null);
            super(null, null, null);

            mPoints = new LinkedList<>();
            for (int i = 0; i < points.length; i += 2) {
+5 −0
Original line number Diff line number Diff line
@@ -23,6 +23,11 @@ public class TestLauncherProvider extends LauncherProvider {
        }
    }

    public SQLiteDatabase getDb() {
        createDbIfNotExists();
        return mOpenHelper.getWritableDatabase();
    }

    @Override
    protected void notifyListeners() { }

+47 −18

File changed.

Preview size limit exceeded, changes collapsed.

Loading