Merge "Bye bye workspace screens table" into ub-launcher3-master

This commit is contained in:
Sunny Goyal 2018-12-10 21:25:18 +00:00 committed by Android (Google) Code Review
commit 5469fad289
23 changed files with 247 additions and 566 deletions

View File

@ -2,8 +2,12 @@
// Note: Comments are not supported in JSON schema, but android parser is lenient.
// Maximum DB version supported by this schema
"version" : 27,
"version" : 28,
"downgrade_to_27" : [
"CREATE TABLE workspaceScreens (_id INTEGER PRIMARY KEY,screenRank INTEGER,modified INTEGER NOT NULL DEFAULT 0)",
"insert into workspaceScreens (_id, screenRank) select screen as _id, screen as screenRank from favorites where container = -100 group by screen order by screen"
],
// Downgrade from 27 to 26. Empty array indicates, the DB is compatible
"downgrade_to_26" : [],
"downgrade_to_25" : [],

View File

@ -18,7 +18,9 @@ import android.util.Pair;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.util.ContentWriter;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
@ -31,6 +33,8 @@ import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Tests for {@link AddWorkspaceItemsTask}
@ -60,15 +64,11 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
for (ItemInfo item : items) {
list.add(Pair.create(item, null));
}
return new AddWorkspaceItemsTask(list) {
@Override
protected void updateScreens(Context context, IntArray workspaceScreens) { }
};
return new AddWorkspaceItemsTask(list);
}
@Test
public void testFindSpaceForItem_prefers_second() {
public void testFindSpaceForItem_prefers_second() throws Exception {
// First screen has only one hole of size 1
int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
@ -93,7 +93,6 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
public void testFindSpaceForItem_adds_new_screen() throws Exception {
// First screen has 2 holes of sizes 3x2 and 2x3
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
commitScreensToDb();
IntArray oldScreens = existingScreens.clone();
int[] spaceFound = newTask()
@ -109,7 +108,6 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
// Setup a screen with a hole
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
commitScreensToDb();
// Nothing was added
assertTrue(executeTaskForTest(newTask(info)).isEmpty());
@ -125,7 +123,6 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
// Setup a screen with a hole
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
commitScreensToDb();
executeTaskForTest(newTask(info, info2)).get(0).run();
ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
@ -141,7 +138,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
assertTrue(animated.getValue().contains(info2));
}
private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) {
private int setupWorkspaceWithHoles(int startId, int screenId, Rect... holes) throws Exception {
GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
for (Rect r : holes) {
@ -151,6 +148,7 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
existingScreens.add(screenId);
screenOccupancy.append(screenId, occupancy);
ExecutorService executor = Executors.newSingleThreadExecutor();
for (int x = 0; x < idp.numColumns; x++) {
for (int y = 0; y < idp.numRows; y++) {
if (!occupancy.cells[x][y]) {
@ -165,27 +163,19 @@ public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
info.cellY = y;
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
bgDataModel.addItem(targetContext, info, false);
executor.execute(() -> {
ContentWriter writer = new ContentWriter(targetContext);
info.writeToValues(writer);
writer.put(Favorites._ID, info.id);
targetContext.getContentResolver().insert(Favorites.CONTENT_URI,
writer.getValues(targetContext));
});
}
}
executor.submit(() -> null).get();
executor.shutdown();
return startId;
}
private void commitScreensToDb() throws Exception {
LauncherSettings.Settings.call(targetContext.getContentResolver(),
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
// Clear the table
ops.add(ContentProviderOperation.newDelete(uri).build());
int count = existingScreens.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
int screenId = existingScreens.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
}
targetContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
}
}

View File

@ -20,14 +20,17 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotSame;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherProvider.DatabaseHelper;
@ -37,14 +40,15 @@ import com.android.launcher3.R;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import java.io.File;
/**
* Tests for {@link DbDowngradeHelper}
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
@RunWith(RobolectricTestRunner.class)
public class DbDowngradeHelperTest {
private static final String SCHEMA_FILE = "test_schema.json";
@ -56,35 +60,47 @@ public class DbDowngradeHelperTest {
@Before
public void setup() {
mContext = InstrumentationRegistry.getTargetContext();
mContext = RuntimeEnvironment.application;
mSchemaFile = mContext.getFileStreamPath(SCHEMA_FILE);
mDbFile = mContext.getDatabasePath(DB_FILE);
}
@Test
public void testDowngradeSchemaMatchesVersion() throws Exception {
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext);
assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
}
@Test
public void testUpdateSchemaFile() throws Exception {
Context myContext = InstrumentationRegistry.getContext();
int testResId = myContext.getResources().getIdentifier(
"db_schema_v10", "raw", myContext.getPackageName());
// Setup mock resources
Resources res = spy(mContext.getResources());
doAnswer(i ->this.getClass().getResourceAsStream("/db_schema_v10.json"))
.when(res).openRawResource(eq(R.raw.downgrade_schema));
Context context = spy(mContext);
when(context.getResources()).thenReturn(res);
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, myContext, testResId);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context);
assertTrue(mSchemaFile.exists());
assertEquals(10, DbDowngradeHelper.parse(mSchemaFile).version);
// Schema is updated on version upgrade
assertTrue(mSchemaFile.setLastModified(0));
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, myContext, testResId);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 11, context);
assertNotSame(0, mSchemaFile.lastModified());
// Schema is not updated when version is same
assertTrue(mSchemaFile.setLastModified(0));
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, myContext, testResId);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 10, context);
assertEquals(0, mSchemaFile.lastModified());
// Schema is not updated on version downgrade
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, myContext, testResId);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 3, context);
assertEquals(0, mSchemaFile.lastModified());
}
@ -143,8 +159,7 @@ public class DbDowngradeHelperTest {
mSchemaFile.delete();
mDbFile.delete();
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext,
R.raw.downgrade_schema);
DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
DatabaseHelper dbHelper = new DatabaseHelper(mContext, null, DB_FILE) {
@Override

View File

@ -1,5 +1,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@ -10,7 +12,6 @@ import android.database.Cursor;
import android.graphics.Point;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FlagOverrideRule;
@ -55,7 +56,6 @@ public class GridSizeMigrationTaskTest {
@Before
public void setUp() {
mValidPackages = new HashSet<>();
mValidPackages.add(TEST_PACKAGE);
mIdp = new InvariantDeviceProfile();
@ -307,11 +307,6 @@ public class GridSizeMigrationTaskTest {
LauncherSettings.Settings.call(mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
mContext.getContentResolver().insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
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];
@ -326,7 +321,6 @@ public class GridSizeMigrationTaskTest {
}
}
IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
return ids;
}
@ -336,7 +330,7 @@ public class GridSizeMigrationTaskTest {
* represent the workspace grid.
*/
private void verifyWorkspace(int[][][] ids) {
IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
IntArray allScreens = getWorkspaceScreenIds(mContext);
assertEquals(ids.length, allScreens.size());
int total = 0;

View File

@ -1747,7 +1747,6 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns,
orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
orderedScreenIds.removeValue(Workspace.FIRST_SCREEN_ID);
orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
} else if (!FeatureFlags.QSB_ON_FIRST_SCREEN.get()
&& orderedScreenIds.isEmpty()) {
// If there are no screens, we need to have an empty screen

View File

@ -20,12 +20,8 @@ import static com.android.launcher3.LauncherAppState.ACTION_FORCE_ROLOAD;
import static com.android.launcher3.config.FeatureFlags.IS_DOGFOOD_BUILD;
import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@ -38,8 +34,8 @@ import android.util.Pair;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AddWorkspaceItemsTask;
import com.android.launcher3.model.BaseModelUpdateTask;
import com.android.launcher3.model.BgDataModel;
@ -51,7 +47,6 @@ import com.android.launcher3.model.PackageInstallStateChangedTask;
import com.android.launcher3.model.PackageUpdatedTask;
import com.android.launcher3.model.ShortcutsChangedTask;
import com.android.launcher3.model.UserLockStateChangedTask;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.util.ComponentKey;
@ -70,7 +65,6 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executor;
@ -268,53 +262,6 @@ public class LauncherModel extends BroadcastReceiver
runOnWorkerThread(r);
}
/**
* Update the order of the workspace screens in the database. The array list contains
* a list of screen ids in the order that they should appear.
*/
public static void updateWorkspaceScreenOrder(Context context, IntArray screens) {
final ContentResolver cr = context.getContentResolver();
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
// Create a copy with only non-negative values
final IntArray screensCopy = new IntArray();
for (int i = 0; i < screens.size(); i++) {
int id = screens.get(i);
if (id >= 0) {
screensCopy.add(id);
}
}
Runnable r = new Runnable() {
@Override
public void run() {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
// Clear the table
ops.add(ContentProviderOperation.newDelete(uri).build());
int count = screensCopy.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
int screenId = screensCopy.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
}
try {
cr.applyBatch(LauncherProvider.AUTHORITY, ops);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
synchronized (sBgDataModel) {
sBgDataModel.workspaceScreens.clear();
sBgDataModel.workspaceScreens.addAll(screensCopy);
}
}
};
runOnWorkerThread(r);
}
/**
* Set this as the current Launcher activity object for the loader.
*/
@ -519,18 +466,6 @@ public class LauncherModel extends BroadcastReceiver
}
}
/**
* Loads the workspace screen ids in an ordered list.
*/
public static IntArray loadWorkspaceScreensDb(Context context) {
final ContentResolver contentResolver = context.getContentResolver();
final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
// Get screens ordered by rank.
return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
}
public void onInstallSessionCreated(final PackageInstallInfo sessionInfo) {
enqueueModelUpdateTask(new BaseModelUpdateTask() {
@Override

View File

@ -32,6 +32,7 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
@ -45,12 +46,12 @@ import android.os.Message;
import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
@ -70,6 +71,7 @@ import java.io.PrintWriter;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
@ -79,8 +81,9 @@ public class LauncherProvider extends ContentProvider {
/**
* Represents the schema of the database. Changes in scheme need not be backwards compatible.
* When increasing the scheme version, ensure that downgrade_schema.json is updated
*/
public static final int SCHEMA_VERSION = 27;
public static final int SCHEMA_VERSION = 28;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
@ -175,10 +178,10 @@ public class LauncherProvider extends ContentProvider {
if (values == null) {
throw new RuntimeException("Error: attempting to insert null values");
}
if (!values.containsKey(LauncherSettings.ChangeLogColumns._ID)) {
if (!values.containsKey(LauncherSettings.Favorites._ID)) {
throw new RuntimeException("Error: attempting to add item without specifying an id");
}
helper.checkId(table, values);
helper.checkId(values);
return (int) db.insert(table, nullColumnHack, values);
}
@ -262,24 +265,7 @@ public class LauncherProvider extends ContentProvider {
}
}
// Add screen id if not present
int screenId = values.getAsInteger(LauncherSettings.Favorites.SCREEN);
SQLiteStatement stmp = null;
try {
stmp = mOpenHelper.getWritableDatabase().compileStatement(
"INSERT OR IGNORE INTO workspaceScreens (_id, screenRank) " +
"select ?, (ifnull(MAX(screenRank), -1)+1) from workspaceScreens");
stmp.bindLong(1, screenId);
ContentValues valuesInserted = new ContentValues();
valuesInserted.put(LauncherSettings.BaseLauncherColumns._ID, stmp.executeInsert());
mOpenHelper.checkId(WorkspaceScreens.TABLE_NAME, valuesInserted);
return true;
} catch (Exception e) {
return false;
} finally {
Utilities.closeSilently(stmp);
}
return true;
}
@Override
@ -404,7 +390,6 @@ public class LauncherProvider extends ContentProvider {
* @return Ids of deleted folders.
*/
private IntArray deleteEmptyFolders() {
IntArray folderIds = new IntArray();
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Select folders whose id do not match any container value.
@ -413,21 +398,19 @@ public class LauncherProvider extends ContentProvider {
+ LauncherSettings.Favorites._ID + " NOT IN (SELECT " +
LauncherSettings.Favorites.CONTAINER + " FROM "
+ Favorites.TABLE_NAME + ")";
try (Cursor c = db.query(Favorites.TABLE_NAME,
new String[] {LauncherSettings.Favorites._ID},
selection, null, null, null, null)) {
LauncherDbUtils.iterateCursor(c, 0, folderIds);
}
IntArray folderIds = LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME,
Favorites._ID, selection, null, null);
if (!folderIds.isEmpty()) {
db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, folderIds), null);
}
t.commit();
return folderIds;
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
folderIds.clear();
return new IntArray();
}
return folderIds;
}
/**
@ -438,7 +421,7 @@ public class LauncherProvider extends ContentProvider {
}
@Thunk static void addModifiedTime(ContentValues values) {
values.put(LauncherSettings.ChangeLogColumns.MODIFIED, System.currentTimeMillis());
values.put(LauncherSettings.Favorites.MODIFIED, System.currentTimeMillis());
}
private void clearFlagEmptyDbCreated() {
@ -551,11 +534,10 @@ public class LauncherProvider extends ContentProvider {
// Table creation sometimes fails silently, which leads to a crash loop.
// This way, we will try to create a table every time after crash, so the device
// would eventually be able to recover.
if (!tableExists(Favorites.TABLE_NAME) || !tableExists(WorkspaceScreens.TABLE_NAME)) {
if (!tableExists(Favorites.TABLE_NAME)) {
Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
// This operation is a no-op if the table already exists.
addFavoritesTable(getWritableDatabase(), true);
addWorkspacesTable(getWritableDatabase(), true);
}
initIds();
@ -602,7 +584,6 @@ public class LauncherProvider extends ContentProvider {
mMaxScreenId = 0;
addFavoritesTable(db, false);
addWorkspacesTable(db, false);
// Fresh and clean launcher DB.
mMaxItemId = initializeMaxItemId(db);
@ -633,46 +614,6 @@ public class LauncherProvider extends ContentProvider {
Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
}
private void addWorkspacesTable(SQLiteDatabase db, boolean optional) {
String ifNotExists = optional ? " IF NOT EXISTS " : "";
db.execSQL("CREATE TABLE " + ifNotExists + WorkspaceScreens.TABLE_NAME + " (" +
LauncherSettings.WorkspaceScreens._ID + " INTEGER PRIMARY KEY," +
LauncherSettings.WorkspaceScreens.SCREEN_RANK + " INTEGER," +
LauncherSettings.ChangeLogColumns.MODIFIED + " INTEGER NOT NULL DEFAULT 0" +
");");
}
private void removeOrphanedItems(SQLiteDatabase db) {
// Delete items directly on the workspace who's screen id doesn't exist
// "DELETE FROM favorites WHERE screen NOT IN (SELECT _id FROM workspaceScreens)
// AND container = -100"
String removeOrphanedDesktopItems = "DELETE FROM " + Favorites.TABLE_NAME +
" WHERE " +
LauncherSettings.Favorites.SCREEN + " NOT IN (SELECT " +
LauncherSettings.WorkspaceScreens._ID + " FROM " + WorkspaceScreens.TABLE_NAME + ")" +
" AND " +
LauncherSettings.Favorites.CONTAINER + " = " +
LauncherSettings.Favorites.CONTAINER_DESKTOP;
db.execSQL(removeOrphanedDesktopItems);
// Delete items contained in folders which no longer exist (after above statement)
// "DELETE FROM favorites WHERE container <> -100 AND container <> -101 AND container
// NOT IN (SELECT _id FROM favorites WHERE itemType = 2)"
String removeOrphanedFolderItems = "DELETE FROM " + Favorites.TABLE_NAME +
" WHERE " +
LauncherSettings.Favorites.CONTAINER + " <> " +
LauncherSettings.Favorites.CONTAINER_DESKTOP +
" AND "
+ LauncherSettings.Favorites.CONTAINER + " <> " +
LauncherSettings.Favorites.CONTAINER_HOTSEAT +
" AND "
+ LauncherSettings.Favorites.CONTAINER + " NOT IN (SELECT " +
LauncherSettings.Favorites._ID + " FROM " + Favorites.TABLE_NAME +
" WHERE " + LauncherSettings.Favorites.ITEM_TYPE + " = " +
LauncherSettings.Favorites.ITEM_TYPE_FOLDER + ")";
db.execSQL(removeOrphanedFolderItems);
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
@ -681,8 +622,7 @@ public class LauncherProvider extends ContentProvider {
if (!schemaFile.exists()) {
handleOneTimeDataUpgrade(db);
}
DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext,
R.raw.downgrade_schema);
DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext);
}
/**
@ -709,12 +649,8 @@ public class LauncherProvider extends ContentProvider {
switch (oldVersion) {
// The version cannot be lower that 12, as Launcher3 never supported a lower
// version of the DB.
case 12: {
// With the new shrink-wrapped and re-orderable workspaces, it makes sense
// to persist workspace screens and their relative order.
mMaxScreenId = 0;
addWorkspacesTable(db, false);
}
case 12:
// No-op
case 13: {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Insert new column for holding widget provider name
@ -728,15 +664,7 @@ public class LauncherProvider extends ContentProvider {
}
}
case 14: {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Insert new column for holding update timestamp
db.execSQL("ALTER TABLE favorites " +
"ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
db.execSQL("ALTER TABLE workspaceScreens " +
"ADD COLUMN modified INTEGER NOT NULL DEFAULT 0;");
t.commit();
} catch (SQLException ex) {
Log.e(TAG, ex.getMessage(), ex);
if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) {
// Old version remains, which means we wipe old data
break;
}
@ -747,22 +675,15 @@ public class LauncherProvider extends ContentProvider {
break;
}
}
case 16: {
case 16:
// No-op
}
case 17: {
case 17:
// No-op
case 18:
// No-op
}
case 18: {
// Due to a data loss bug, some users may have items associated with screen ids
// which no longer exist. Since this can cause other problems, and since the user
// will never see these items anyway, we use database upgrade as an opportunity to
// clean things up.
removeOrphanedItems(db);
}
case 19: {
// Add userId column
if (!addProfileColumn(db)) {
if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) {
// Old version remains, which means we wipe old data
break;
}
@ -772,10 +693,7 @@ public class LauncherProvider extends ContentProvider {
break;
}
case 21:
// Recreate workspace table with screen id a primary key
if (!recreateWorkspaceTable(db)) {
break;
}
// No-op
case 22: {
if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
// Old version remains, which means we wipe old data
@ -794,7 +712,30 @@ public class LauncherProvider extends ContentProvider {
!LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
break;
}
case 27:
case 27: {
// Update the favorites table so that the screen ids are ordered based on
// workspace page rank.
IntArray finalScreens = LauncherDbUtils.queryIntArray(db, "workspaceScreens",
BaseColumns._ID, null, null, "screenRank");
int[] original = finalScreens.toArray();
Arrays.sort(original);
String updatemap = "";
for (int i = 0; i < original.length; i++) {
if (finalScreens.get(i) != original[i]) {
updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d",
Favorites.SCREEN, finalScreens.get(i), original[i]);
}
}
if (!TextUtils.isEmpty(updatemap)) {
String query = String.format(Locale.ENGLISH,
"UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d",
Favorites.TABLE_NAME, Favorites.SCREEN, updatemap,
Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
db.execSQL(query);
}
db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
}
case 28:
// DB Upgraded successfully
return;
}
@ -822,7 +763,7 @@ public class LauncherProvider extends ContentProvider {
public void createEmptyDB(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
db.execSQL("DROP TABLE IF EXISTS " + Favorites.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
db.execSQL("DROP TABLE IF EXISTS workspaceScreens");
onCreate(db);
t.commit();
}
@ -845,17 +786,9 @@ public class LauncherProvider extends ContentProvider {
Log.e(TAG, "getAppWidgetIds not supported", e);
return;
}
final IntSet validWidgets = new IntSet();
try (Cursor c = db.query(Favorites.TABLE_NAME,
new String[] {Favorites.APPWIDGET_ID },
"itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null, null, null)) {
while (c.moveToNext()) {
validWidgets.add(c.getInt(0));
}
} catch (SQLException ex) {
Log.w(TAG, "Error getting widgets list", ex);
return;
}
final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db,
Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
"itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
for (int widgetId : allWidgets) {
if (!validWidgets.contains(widgetId)) {
try {
@ -910,46 +843,6 @@ public class LauncherProvider extends ContentProvider {
}
}
/**
* Recreates workspace table and migrates data to the new table.
*/
public boolean recreateWorkspaceTable(SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
final IntArray sortedIDs;
try (Cursor c = db.query(WorkspaceScreens.TABLE_NAME,
new String[] {LauncherSettings.WorkspaceScreens._ID},
null, null, null, null,
LauncherSettings.WorkspaceScreens.SCREEN_RANK)) {
// Use LinkedHashSet so that ordering is preserved
sortedIDs = LauncherDbUtils.getScreenIdsFromCursor(c);
}
db.execSQL("DROP TABLE IF EXISTS " + WorkspaceScreens.TABLE_NAME);
addWorkspacesTable(db, false);
// Add all screen ids back
int total = sortedIDs.size();
for (int i = 0; i < total; i++) {
ContentValues values = new ContentValues();
values.put(LauncherSettings.WorkspaceScreens._ID, sortedIDs.get(i));
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
addModifiedTime(values);
db.insertOrThrow(WorkspaceScreens.TABLE_NAME, null, values);
}
t.commit();
mMaxScreenId = 0;
for (int i = 0; i < sortedIDs.size(); i++) {
mMaxScreenId = Math.max(mMaxScreenId, sortedIDs.get(i));
}
} catch (SQLException ex) {
// Old version remains, which means we wipe old data
Log.e(TAG, ex.getMessage(), ex);
return false;
}
return true;
}
@Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
if (addRankColumn) {
@ -979,10 +872,6 @@ public class LauncherProvider extends ContentProvider {
return true;
}
private boolean addProfileColumn(SQLiteDatabase db) {
return addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial());
}
private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
db.execSQL("ALTER TABLE favorites ADD COLUMN "
@ -1018,17 +907,20 @@ public class LauncherProvider extends ContentProvider {
return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
}
public void checkId(String table, ContentValues values) {
int id = values.getAsInteger(LauncherSettings.BaseLauncherColumns._ID);
if (WorkspaceScreens.TABLE_NAME.equals(table)) {
mMaxScreenId = Math.max(id, mMaxScreenId);
} else {
mMaxItemId = Math.max(id, mMaxItemId);
public void checkId(ContentValues values) {
int id = values.getAsInteger(Favorites._ID);
mMaxItemId = Math.max(id, mMaxItemId);
Integer screen = values.getAsInteger(Favorites.SCREEN);
Integer container = values.getAsInteger(Favorites.CONTAINER);
if (screen != null && container != null
&& container.intValue() == Favorites.CONTAINER_DESKTOP) {
mMaxScreenId = Math.max(screen, mMaxScreenId);
}
}
private int initializeMaxItemId(SQLiteDatabase db) {
return getMaxId(db, Favorites.TABLE_NAME);
return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME);
}
// Generates a new ID to use for an workspace screen in your database. This method
@ -1045,34 +937,18 @@ public class LauncherProvider extends ContentProvider {
}
private int initializeMaxScreenId(SQLiteDatabase db) {
return getMaxId(db, WorkspaceScreens.TABLE_NAME);
return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP);
}
@Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
IntArray screenIds = new IntArray();
// TODO: Use multiple loaders with fall-back and transaction.
int count = loader.loadLayout(db, screenIds);
// Add the screens specified by the items above
int[] sortedScreenIds = screenIds.toArray();
Arrays.sort(sortedScreenIds);
int rank = 0;
ContentValues values = new ContentValues();
for (int id : sortedScreenIds) {
values.clear();
values.put(LauncherSettings.WorkspaceScreens._ID, id);
values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, rank);
if (dbInsertAndCheck(this, db, WorkspaceScreens.TABLE_NAME, null, values) < 0) {
throw new RuntimeException("Failed initialize screen table"
+ "from default layout");
}
rank++;
}
int count = loader.loadLayout(db, new IntArray());
// Ensure that the max ids are initialized
mMaxItemId = initializeMaxItemId(db);
mMaxScreenId = initializeMaxScreenId(db);
return count;
}
}
@ -1080,22 +956,14 @@ public class LauncherProvider extends ContentProvider {
/**
* @return the max _id in the provided table.
*/
@Thunk static int getMaxId(SQLiteDatabase db, String table) {
Cursor c = db.rawQuery("SELECT MAX(_id) FROM " + table, null);
// get the result
int id = -1;
if (c != null && c.moveToNext()) {
id = c.getInt(0);
@Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) {
int max = (int) DatabaseUtils.longForQuery(db,
String.format(Locale.ENGLISH, query, args),
null);
if (max < 0) {
throw new RuntimeException("Error: could not query max id");
}
if (c != null) {
c.close();
}
if (id == -1) {
throw new RuntimeException("Error: could not query max id in " + table);
}
return id;
return max;
}
static class SqlArguments {

View File

@ -26,16 +26,17 @@ import android.provider.BaseColumns;
* Settings related utilities.
*/
public class LauncherSettings {
/** Columns required on table staht will be subject to backup and restore. */
static interface ChangeLogColumns extends BaseColumns {
/**
* Favorites.
*/
public static final class Favorites implements BaseColumns {
/**
* The time of the last update to this row.
* <P>Type: INTEGER</P>
*/
public static final String MODIFIED = "modified";
}
static public interface BaseLauncherColumns extends ChangeLogColumns {
/**
* Descriptive name of the gesture that can be displayed to the user.
* <P>Type: TEXT</P>
@ -84,34 +85,6 @@ public class LauncherSettings {
* <P>Type: BLOB</P>
*/
public static final String ICON = "icon";
}
/**
* Workspace Screens.
*
* Tracks the order of workspace screens.
*/
public static final class WorkspaceScreens implements ChangeLogColumns {
public static final String TABLE_NAME = "workspaceScreens";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse("content://" +
LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
/**
* The rank of this screen -- ie. how it is ordered relative to the other screens.
* <P>Type: INTEGER</P>
*/
public static final String SCREEN_RANK = "screenRank";
}
/**
* Favorites.
*/
public static final class Favorites implements BaseLauncherColumns {
public static final String TABLE_NAME = "favorites";

View File

@ -150,7 +150,7 @@ public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmList
Intent intent = null;
UserHandle user = null;
if (item != null &&
item.itemType == LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION) {
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
intent = item.getIntent();
user = item.user;
}

View File

@ -89,7 +89,7 @@ public class ShortcutInfo extends ItemInfoWithIcon {
private int mInstallProgress;
public ShortcutInfo() {
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT;
}
public ShortcutInfo(ShortcutInfo info) {
@ -114,24 +114,23 @@ public class ShortcutInfo extends ItemInfoWithIcon {
@TargetApi(Build.VERSION_CODES.N)
public ShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
user = shortcutInfo.getUserHandle();
itemType = LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
itemType = Favorites.ITEM_TYPE_DEEP_SHORTCUT;
updateFromDeepShortcutInfo(shortcutInfo, context);
}
@Override
public void onAddToDatabase(ContentWriter writer) {
super.onAddToDatabase(writer);
writer.put(LauncherSettings.BaseLauncherColumns.TITLE, title)
.put(LauncherSettings.BaseLauncherColumns.INTENT, getIntent())
.put(LauncherSettings.Favorites.RESTORED, status);
writer.put(Favorites.TITLE, title)
.put(Favorites.INTENT, getIntent())
.put(Favorites.RESTORED, status);
if (!usingLowResIcon()) {
writer.putIcon(iconBitmap, user);
}
if (iconResource != null) {
writer.put(LauncherSettings.BaseLauncherColumns.ICON_PACKAGE, iconResource.packageName)
.put(LauncherSettings.BaseLauncherColumns.ICON_RESOURCE,
iconResource.resourceName);
writer.put(Favorites.ICON_PACKAGE, iconResource.packageName)
.put(Favorites.ICON_RESOURCE, iconResource.resourceName);
}
}
@ -189,7 +188,7 @@ public class ShortcutInfo extends ItemInfoWithIcon {
@Override
public ComponentName getTargetComponent() {
ComponentName cn = super.getTargetComponent();
if (cn == null && (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT
if (cn == null && (itemType == Favorites.ITEM_TYPE_SHORTCUT
|| hasStatusFlag(FLAG_SUPPORTS_WEB_UI))) {
// Legacy shortcuts and promise icons with web UI may not have a componentName but just
// a packageName. In that case create a dummy componentName instead of adding additional

View File

@ -619,9 +619,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
// if this is the last screen, convert it to the empty screen
mWorkspaceScreens.put(EXTRA_EMPTY_SCREEN_ID, finalScreen);
mScreenOrder.add(EXTRA_EMPTY_SCREEN_ID);
// Update the model if we have changed any screens
LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
}
}
@ -728,9 +725,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
mWorkspaceScreens.put(newId, cl);
mScreenOrder.add(newId);
// Update the model for the new screen
LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder);
return newId;
}
@ -828,13 +822,6 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
}
}
if (!removeScreens.isEmpty()) {
// Update the model if we have changed any screens
mLauncher.getModelWriter().enqueueDeleteRunnable(
() -> LauncherModel.updateWorkspaceScreenOrder(mLauncher, mScreenOrder));
}
if (pageShift >= 0) {
setCurrentPage(currentPage - pageShift);
}

View File

@ -15,11 +15,11 @@
*/
package com.android.launcher3.model;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.util.LongSparseArray;
import android.util.Pair;
import com.android.launcher3.AllAppsList;
import com.android.launcher3.AppInfo;
import com.android.launcher3.FolderInfo;
@ -27,7 +27,6 @@ import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherModel.Callbacks;
import com.android.launcher3.LauncherSettings;
@ -58,16 +57,12 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
if (mItemList.isEmpty()) {
return;
}
Context context = app.getContext();
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
final IntArray addedWorkspaceScreensFinal = new IntArray();
// Get the list of workspace screens. We need to append to this list and
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
// called.
IntArray workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
synchronized(dataModel) {
IntArray workspaceScreens = dataModel.workspaceScreens.clone();
List<ItemInfo> filteredItems = new ArrayList<>();
for (Pair<ItemInfo, Object> entry : mItemList) {
@ -116,9 +111,6 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
}
}
// Update the workspace screens
updateScreens(context, workspaceScreens);
if (!addedItemsFinal.isEmpty()) {
scheduleCallbackTask(new CallbackTask() {
@Override
@ -143,10 +135,6 @@ public class AddWorkspaceItemsTask extends BaseModelUpdateTask {
}
}
protected void updateScreens(Context context, IntArray workspaceScreens) {
LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
}
/**
* Returns true if the shortcuts already exists on the workspace. This must be called after
* the workspace has been loaded. We identify a shortcut by its intent.

View File

@ -21,6 +21,7 @@ import android.database.sqlite.SQLiteException;
import android.util.Log;
import android.util.SparseArray;
import com.android.launcher3.R;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.util.IOUtils;
@ -87,8 +88,7 @@ public class DbDowngradeHelper {
return helper;
}
public static void updateSchemaFile(File schemaFile, int expectedVersion,
Context context, int schemaResId) {
public static void updateSchemaFile(File schemaFile, int expectedVersion, Context context) {
try {
if (DbDowngradeHelper.parse(schemaFile).version >= expectedVersion) {
return;
@ -99,7 +99,7 @@ public class DbDowngradeHelper {
// Write the updated schema
try (FileOutputStream fos = new FileOutputStream(schemaFile);
InputStream in = context.getResources().openRawResource(schemaResId)) {
InputStream in = context.getResources().openRawResource(R.raw.downgrade_schema)) {
IOUtils.copy(in, fos);
} catch (IOException e) {
Log.e(TAG, "Error writing schema file", e);

View File

@ -13,14 +13,12 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Point;
import android.net.Uri;
import android.util.Log;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@ -31,12 +29,14 @@ import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.GridOccupancy;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.IntSparseArrayMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Locale;
import androidx.annotation.VisibleForTesting;
/**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@ -180,11 +180,25 @@ public class GridSizeMigrationTask {
return applyOperations();
}
@VisibleForTesting
static IntArray getWorkspaceScreenIds(Context context) {
IntSet set = new IntSet();
try (Cursor c = context.getContentResolver().query(Favorites.CONTENT_URI,
new String[] {Favorites.SCREEN},
Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
null, Favorites.SCREEN)) {
while (c.moveToNext()) {
set.add(c.getInt(0));
}
}
return set.getArray();
}
/**
* @return true if any DB change was made
*/
protected boolean migrateWorkspace() throws Exception {
IntArray allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
IntArray allScreens = getWorkspaceScreenIds(mContext);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
}
@ -216,9 +230,8 @@ public class GridSizeMigrationTask {
int newScreenId = LauncherSettings.Settings.call(
mContext.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
allScreens.add(newScreenId);
for (DbEntry item : placement.finalPlacedItems) {
if (!mCarryOver.remove(itemMap.get(item.id))) {
throw new Exception("Unable to find matching items");
@ -231,19 +244,6 @@ public class GridSizeMigrationTask {
}
} while (!mCarryOver.isEmpty());
// Update screens
final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
mUpdateOperations.add(ContentProviderOperation.newDelete(uri).build());
int count = allScreens.size();
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
int screenId = allScreens.get(i);
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(
v).build());
}
}
return applyOperations();
}

View File

@ -380,7 +380,7 @@ public class LoaderCursor extends CursorWrapper {
* otherwise marks it for deletion.
*/
public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
if (checkItemPlacement(info, dataModel.workspaceScreens)) {
if (checkItemPlacement(info)) {
dataModel.addItem(mContext, info, false);
} else {
markDeleted("Item position overlap");
@ -390,7 +390,7 @@ public class LoaderCursor extends CursorWrapper {
/**
* check & update map of what's occupied; used to discard overlapping/invalid items
*/
protected boolean checkItemPlacement(ItemInfo item, IntArray workspaceScreens) {
protected boolean checkItemPlacement(ItemInfo item) {
int containerIndex = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
final GridOccupancy hotseatOccupancy =
@ -420,12 +420,7 @@ public class LoaderCursor extends CursorWrapper {
occupied.put(LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy);
return true;
}
} else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
if (!workspaceScreens.contains(item.screenId)) {
// The item has an invalid screen id.
return false;
}
} else {
} else if (item.container != LauncherSettings.Favorites.CONTAINER_DESKTOP) {
// Skip further checking if it is not the hotseat or workspace container
return true;
}

View File

@ -72,6 +72,7 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import com.android.launcher3.util.LooperIdleLock;
import com.android.launcher3.util.MultiHashMap;
import com.android.launcher3.util.PackageManagerHelper;
@ -293,7 +294,6 @@ public class LoaderTask implements Runnable {
final HashMap<String, SessionInfo> installingPkgs =
mPackageInstaller.updateAndGetActiveSessionCache();
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
mBgDataModel.workspaceScreens.addAll(LauncherModel.loadWorkspaceScreensDb(context));
Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(contentResolver.query(
@ -780,21 +780,15 @@ public class LoaderTask implements Runnable {
new Handler(LauncherModel.getWorkerLooper()));
}
// Remove any empty screens
IntArray unusedScreens = mBgDataModel.workspaceScreens.clone();
// Initialize the screens array. Using an InstSet ensures that the screen ids
// are sorted.
IntSet screenSet = new IntSet();
for (ItemInfo item: mBgDataModel.itemsIdMap) {
int screenId = item.screenId;
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
unusedScreens.contains(screenId)) {
unusedScreens.removeValue(screenId);
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
screenSet.add(item.screenId);
}
}
// If there are any empty screens remove them, and update.
if (unusedScreens.size() != 0) {
mBgDataModel.workspaceScreens.removeAllValues(unusedScreens);
LauncherModel.updateWorkspaceScreenOrder(context, mBgDataModel.workspaceScreens);
}
mBgDataModel.workspaceScreens.addAll(screenSet.getArray());
}
}

View File

@ -333,7 +333,7 @@ public class ModelWriter {
* {@link #commitDelete()} is called (or abandoned if {@link #abortDelete()} is called).
* Otherwise, we run the Runnable immediately.
*/
public void enqueueDeleteRunnable(Runnable r) {
private void enqueueDeleteRunnable(Runnable r) {
if (mPreparingToUndo) {
mDeleteRunnables.add(r);
} else {

View File

@ -33,7 +33,6 @@ import android.os.Process;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.DefaultLayoutParser;
@ -43,7 +42,6 @@ import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.compat.UserManagerCompat;
@ -72,7 +70,6 @@ public class ImportDataTask {
private final Context mContext;
private final Uri mOtherScreensUri;
private final Uri mOtherFavoritesUri;
private int mHotseatSize;
@ -81,41 +78,14 @@ public class ImportDataTask {
private ImportDataTask(Context context, String sourceAuthority) {
mContext = context;
mOtherScreensUri = Uri.parse("content://" +
sourceAuthority + "/" + WorkspaceScreens.TABLE_NAME);
mOtherFavoritesUri = Uri.parse("content://" + sourceAuthority + "/" + Favorites.TABLE_NAME);
}
public boolean importWorkspace() throws Exception {
IntArray allScreens = LauncherDbUtils.getScreenIdsFromCursor(
mContext.getContentResolver().query(mOtherScreensUri, null, null, null,
LauncherSettings.WorkspaceScreens.SCREEN_RANK));
FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri);
// During import we reset the screen IDs to 0-indexed values.
if (allScreens.isEmpty()) {
// No thing to migrate
FileLog.e(TAG, "No data found to import");
return false;
}
mHotseatSize = mMaxGridSizeX = mMaxGridSizeY = 0;
// Build screen update
ArrayList<ContentProviderOperation> screenOps = new ArrayList<>();
int count = allScreens.size();
SparseIntArray screenIdMap = new SparseIntArray(count);
for (int i = 0; i < count; i++) {
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, i);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
screenIdMap.put(allScreens.get(i), i);
screenOps.add(ContentProviderOperation.newInsert(
LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build());
}
mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, screenOps);
importWorkspaceItems(allScreens.get(0), screenIdMap);
importWorkspaceItems();
GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize);
// Create empty DB flag.
@ -129,17 +99,17 @@ public class ImportDataTask {
* 2) For home screen entries, maps the screen id based on {@param screenIdMap}
* 3) In the end fills any holes in hotseat with items from default hotseat layout.
*/
private void importWorkspaceItems(
int firstScreenId, SparseIntArray screenIdMap) throws Exception {
private void importWorkspaceItems() throws Exception {
String profileId = Long.toString(UserManagerCompat.getInstance(mContext)
.getSerialNumberForUser(Process.myUserHandle()));
boolean createEmptyRowOnFirstScreen;
if (FeatureFlags.QSB_ON_FIRST_SCREEN.get()) {
try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
// get items on the first row of the first screen
"profileId = ? AND container = -100 AND screen = ? AND cellY = 0",
new String[]{profileId, Integer.toString(firstScreenId)},
// get items on the first row of the first screen (min screen id)
"profileId = ? AND container = -100 AND cellY = 0 AND screen = " +
"(SELECT MIN(screen) FROM favorites WHERE container = -100)",
new String[]{profileId},
null)) {
// First row of first screen is not empty
createEmptyRowOnFirstScreen = c.moveToNext();
@ -163,7 +133,7 @@ public class ImportDataTask {
Favorites.PROFILE_ID + " = ?", new String[]{profileId},
// Get the items sorted by container, so that the folders are loaded
// before the corresponding items.
Favorites.CONTAINER)) {
Favorites.CONTAINER + " , " + Favorites.SCREEN)) {
// various columns we expect to exist.
final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
@ -185,6 +155,7 @@ public class ImportDataTask {
SparseBooleanArray mValidFolders = new SparseBooleanArray();
ContentValues values = new ContentValues();
Integer firstScreenId = null;
while (c.moveToNext()) {
values.clear();
int id = c.getInt(idIndex);
@ -201,16 +172,21 @@ public class ImportDataTask {
switch (container) {
case Favorites.CONTAINER_DESKTOP: {
Integer newScreenId = screenIdMap.get(screen);
if (newScreenId == null) {
FileLog.d(TAG, String.format("Skipping item %d, type %d not on a valid screen %d", id, type, screen));
if (screen < Workspace.FIRST_SCREEN_ID) {
FileLog.d(TAG, String.format(
"Skipping item %d, type %d not on a valid screen %d",
id, type, screen));
continue;
}
if (firstScreenId == null) {
firstScreenId = screen;
}
// Reset the screen to 0-index value
screen = newScreenId;
if (createEmptyRowOnFirstScreen && screen == Workspace.FIRST_SCREEN_ID) {
if (createEmptyRowOnFirstScreen && firstScreenId.equals(screen)) {
// Shift items by 1.
cellY++;
// Change the screen id to first screen
screen = Workspace.FIRST_SCREEN_ID;
}
mMaxGridSizeX = Math.max(mMaxGridSizeX, cellX + spanX);
@ -218,7 +194,7 @@ public class ImportDataTask {
break;
}
case Favorites.CONTAINER_HOTSEAT: {
mHotseatSize = Math.max(mHotseatSize, (int) screen + 1);
mHotseatSize = Math.max(mHotseatSize, screen + 1);
break;
}
default:

View File

@ -25,11 +25,9 @@ import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.LauncherSettings.WorkspaceScreens;
import com.android.launcher3.util.IntArray;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
/**
* A set of utility methods for Launcher DB used for DB updates and migration.
@ -47,26 +45,25 @@ public class LauncherDbUtils {
*/
public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) {
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
// Get the existing screens
IntArray screenIds = getScreenIdsFromCursor(db.query(WorkspaceScreens.TABLE_NAME,
null, null, null, null, null, WorkspaceScreens.SCREEN_RANK));
// Get the first screen
final int firstScreenId;
try (Cursor c = db.rawQuery(String.format(Locale.ENGLISH,
"SELECT MIN(%1$s) from %2$s where %3$s = %4$d",
Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
Favorites.CONTAINER_DESKTOP), null)) {
if (screenIds.isEmpty()) {
// No update needed
t.commit();
return true;
}
if (screenIds.get(0) != 0) {
// First screen is not 0, we need to rename screens
if (screenIds.contains(0)) {
// There is already a screen 0. First rename it to a different screen.
int newScreenId = 1;
while (screenIds.contains(newScreenId)) newScreenId++;
renameScreen(db, 0, newScreenId);
if (!c.moveToNext()) {
// No update needed
t.commit();
return true;
}
firstScreenId = c.getInt(0);
}
if (firstScreenId != 0) {
// Rename the first screen to 0.
renameScreen(db, screenIds.get(0), 0);
renameScreen(db, firstScreenId, 0);
}
// Check if the first row is empty
@ -89,31 +86,19 @@ public class LauncherDbUtils {
private static void renameScreen(SQLiteDatabase db, int oldScreen, int newScreen) {
String[] whereParams = new String[] { Integer.toString(oldScreen) };
ContentValues values = new ContentValues();
values.put(WorkspaceScreens._ID, newScreen);
db.update(WorkspaceScreens.TABLE_NAME, values, "_id = ?", whereParams);
values.clear();
values.put(Favorites.SCREEN, newScreen);
db.update(Favorites.TABLE_NAME, values, "container = -100 and screen = ?", whereParams);
}
/**
* Parses the cursor containing workspace screens table and returns the list of screen IDs
*/
public static IntArray getScreenIdsFromCursor(Cursor sc) {
try {
return iterateCursor(sc,
sc.getColumnIndexOrThrow(WorkspaceScreens._ID), new IntArray());
} finally {
sc.close();
}
}
public static IntArray iterateCursor(Cursor c, int columnIndex, IntArray out) {
while (c.moveToNext()) {
out.add(c.getInt(columnIndex));
public static IntArray queryIntArray(SQLiteDatabase db, String tableName, String columnName,
String selection, String groupBy, String orderBy) {
IntArray out = new IntArray();
try (Cursor c = db.query(tableName, new String[] { columnName }, selection, null,
groupBy, null, orderBy)) {
while (c.moveToNext()) {
out.add(c.getInt(0));
}
}
return out;
}

View File

@ -48,4 +48,15 @@ public class IntSet {
public int size() {
return mArray.size();
}
public IntArray getArray() {
return mArray;
}
public static IntSet wrap(IntArray array) {
IntSet set = new IntSet();
set.mArray.addAll(array);
Arrays.sort(set.mArray.mValues, 0, set.mArray.mSize);
return set;
}
}

View File

@ -18,16 +18,12 @@ import com.android.launcher3.ShortcutInfo;
import com.android.launcher3.Utilities;
import com.android.launcher3.compat.LauncherAppsCompat;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.util.IntArray;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
import static com.android.launcher3.LauncherSettings.BaseLauncherColumns.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.INTENT;
import static com.android.launcher3.LauncherSettings.Favorites.CELLX;
import static com.android.launcher3.LauncherSettings.Favorites.CELLY;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER;
@ -148,81 +144,57 @@ public class LoaderCursorTest {
assertEquals(ITEM_TYPE_SHORTCUT, info.itemType);
}
@Test
public void checkItemPlacement_wrongWorkspaceScreen() {
IntArray workspaceScreens = IntArray.wrap(1, 3);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Item on unknown screen are not placed
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5), workspaceScreens));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3), workspaceScreens));
}
@Test
public void checkItemPlacement_outsideBounds() {
IntArray workspaceScreens = IntArray.wrap(1, 2);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Item outside screen bounds are not placed
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1)));
}
@Test
public void checkItemPlacement_overlappingItems() {
IntArray workspaceScreens = IntArray.wrap(1, 2);
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Overlapping items are not placed
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1)));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2), workspaceScreens));
newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2)));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1)));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1)));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1), workspaceScreens));
newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1)));
}
@Test
public void checkItemPlacement_hotseat() {
IntArray workspaceScreens = new IntArray();
mIDP.numRows = 4;
mIDP.numColumns = 4;
mIDP.numHotseatIcons = 3;
// Hotseat items are only placed based on screenId
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1)));
assertTrue(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2)));
assertFalse(mLoaderCursor.checkItemPlacement(
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), workspaceScreens));
newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3)));
}
private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,

View File

@ -297,10 +297,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
if (screenId > Workspace.FIRST_SCREEN_ID) {
screenId = Workspace.FIRST_SCREEN_ID;
}
ContentValues v = new ContentValues();
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, 0);
mResolver.insert(LauncherSettings.WorkspaceScreens.CONTENT_URI, v);
// Insert the item
ContentWriter writer = new ContentWriter(mTargetContext);