Extending the grid migration logic to handle density changes
For hotseat migratino, we simply drop the items with least weight
If the workspace row/column decreases by 2 or more, we clear the whole workspace
Bug: 25958224
Change-Id: I7131b955023d185ed10955f593184b9238546dc8
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index a91181d..56c0192 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -73,12 +73,12 @@
/**
* Number of icons inside the hotseat area.
*/
- int numHotseatIcons;
+ public int numHotseatIcons;
float hotseatIconSize;
int defaultLayoutId;
// Derived invariant properties
- int hotseatAllAppsRank;
+ public int hotseatAllAppsRank;
DeviceProfile landscapeProfile;
DeviceProfile portraitProfile;
diff --git a/src/com/android/launcher3/LauncherBackupAgentHelper.java b/src/com/android/launcher3/LauncherBackupAgentHelper.java
index 0773bf2..bf9c668 100644
--- a/src/com/android/launcher3/LauncherBackupAgentHelper.java
+++ b/src/com/android/launcher3/LauncherBackupAgentHelper.java
@@ -24,7 +24,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import java.io.IOException;
@@ -101,8 +101,9 @@
LauncherSettings.Settings.METHOD_UPDATE_FOLDER_ITEMS_RANK);
}
- if (MigrateFromRestoreTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
- MigrateFromRestoreTask.markForMigration(getApplicationContext(),
+ // TODO: Update this logic to handle grid difference of 2. as well as hotseat difference
+ if (GridSizeMigrationTask.ENABLED && mHelper.shouldAttemptWorkspaceMigration()) {
+ GridSizeMigrationTask.markForMigration(getApplicationContext(),
(int) mHelper.migrationCompatibleProfileData.desktopCols,
(int) mHelper.migrationCompatibleProfileData.desktopRows,
mHelper.widgetSizes);
diff --git a/src/com/android/launcher3/LauncherBackupHelper.java b/src/com/android/launcher3/LauncherBackupHelper.java
index 509fbf8..4ebead5 100644
--- a/src/com/android/launcher3/LauncherBackupHelper.java
+++ b/src/com/android/launcher3/LauncherBackupHelper.java
@@ -52,7 +52,7 @@
import com.android.launcher3.backup.BackupProtos.Widget;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.util.Thunk;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano;
@@ -315,7 +315,7 @@
return true;
}
- if (MigrateFromRestoreTask.ENABLED &&
+ if (GridSizeMigrationTask.ENABLED &&
(oldProfile.desktopCols - currentProfile.desktopCols <= 1) &&
(oldProfile.desktopRows - currentProfile.desktopRows <= 1)) {
// Allow desktop migration when row and/or column count contracts by 1.
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index fe0abc0..0eb1a90 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -57,7 +57,7 @@
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.compat.UserManagerCompat;
import com.android.launcher3.config.ProviderConfig;
-import com.android.launcher3.model.MigrateFromRestoreTask;
+import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.CursorIconInfo;
@@ -1651,14 +1651,14 @@
int countX = profile.numColumns;
int countY = profile.numRows;
- if (MigrateFromRestoreTask.ENABLED && MigrateFromRestoreTask.shouldRunTask(mContext)) {
+ if (GridSizeMigrationTask.ENABLED && GridSizeMigrationTask.shouldRunTask(mContext)) {
long migrationStartTime = System.currentTimeMillis();
Log.v(TAG, "Starting workspace migration after restore");
try {
- MigrateFromRestoreTask task = new MigrateFromRestoreTask(mContext);
+ GridSizeMigrationTask task = new GridSizeMigrationTask(mContext);
// Clear the flags before starting the task, so that we do not run the task
// again, in case there was an uncaught error.
- MigrateFromRestoreTask.clearFlags(mContext);
+ GridSizeMigrationTask.clearFlags(mContext);
task.execute();
} catch (Exception e) {
Log.e(TAG, "Error during grid migration", e);
@@ -1668,6 +1668,8 @@
}
Log.v(TAG, "Workspace migration completed in "
+ (System.currentTimeMillis() - migrationStartTime));
+
+ GridSizeMigrationTask.saveCurrentConfig(mContext);
}
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
diff --git a/src/com/android/launcher3/model/MigrateFromRestoreTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
similarity index 78%
rename from src/com/android/launcher3/model/MigrateFromRestoreTask.java
rename to src/com/android/launcher3/model/GridSizeMigrationTask.java
index 9cabc8d..08c3dc0 100644
--- a/src/com/android/launcher3/model/MigrateFromRestoreTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -24,29 +24,33 @@
import com.android.launcher3.compat.PackageInstallerCompat;
import com.android.launcher3.compat.UserHandleCompat;
import com.android.launcher3.util.LongArrayMap;
-import com.android.launcher3.util.Thunk;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Locale;
/**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
- * result of restoring from a larger device.
+ * result of restoring from a larger device or device density change.
*/
-public class MigrateFromRestoreTask {
+public class GridSizeMigrationTask {
- public static boolean ENABLED = false;
+ public static boolean ENABLED = Utilities.isNycOrAbove();
- private static final String TAG = "MigrateFromRestoreTask";
+ private static final String TAG = "GridSizeMigrationTask";
private static final boolean DEBUG = true;
- private static final String KEY_MIGRATION_SOURCE_SIZE = "migration_restore_src_size";
+ private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
+ private static final String KEY_MIGRATION_SRC_HOTSEAT_SIZE = "migration_src_hotseat_size";
+
+ // Set of entries indicating minimum size a widget can be resized to. This is used during
+ // restore in case the widget has not been installed yet.
private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
// These are carefully selected weights for various item types (Math.random?), to allow for
- // the lease absurd migration experience.
+ // the least absurd migration experience.
private static final float WT_SHORTCUT = 1;
private static final float WT_APPLICATION = 0.8f;
private static final float WT_WIDGET_MIN = 2;
@@ -65,17 +69,37 @@
private ArrayList<DbEntry> mCarryOver;
private final int mSrcX, mSrcY;
- @Thunk final int mTrgX, mTrgY;
+ private final int mTrgX, mTrgY;
private final boolean mShouldRemoveX, mShouldRemoveY;
- public MigrateFromRestoreTask(Context context) {
+ private final int mSrcHotseatSize;
+ private final int mSrcAllAppsRank;
+
+ /**
+ * TODO: Create a generic constructor which can be unit tested.
+ */
+ public GridSizeMigrationTask(Context context) {
mContext = context;
+
+ mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ mTrgX = mIdp.numColumns;
+ mTrgY = mIdp.numRows;
+
SharedPreferences prefs = Utilities.getPrefs(context);
- Point sourceSize = parsePoint(prefs.getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ Point sourceSize = parsePoint(
+ prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(mTrgX, mTrgY)));
mSrcX = sourceSize.x;
mSrcY = sourceSize.y;
+ // Hotseat
+ Point hotseatSize = parsePoint(
+ prefs.getString(KEY_MIGRATION_SRC_HOTSEAT_SIZE,
+ getPointString(mIdp.numHotseatIcons, mIdp.hotseatAllAppsRank)));
+ mSrcHotseatSize = hotseatSize.x;
+ mSrcAllAppsRank = hotseatSize.y;
+
+ // Widget sizes
mWidgetMinSize = new HashMap<String, Point>();
for (String s : prefs.getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
Collections.<String>emptySet())) {
@@ -83,16 +107,12 @@
mWidgetMinSize.put(parts[0], parsePoint(parts[1]));
}
- mIdp = LauncherAppState.getInstance().getInvariantDeviceProfile();
- mTrgX = mIdp.numColumns;
- mTrgY = mIdp.numRows;
mShouldRemoveX = mTrgX < mSrcX;
mShouldRemoveY = mTrgY < mSrcY;
}
public void execute() throws Exception {
mEntryToRemove = new ArrayList<>();
- mCarryOver = new ArrayList<>();
mUpdateOperations = new ArrayList<>();
// Initialize list of valid packages. This contain all the packages which are already on
@@ -107,6 +127,97 @@
mValidPackages.addAll(PackageInstallerCompat.getInstance(mContext)
.updateAndGetActiveSessionCache().keySet());
+ // Migrate hotseat
+ if (mSrcHotseatSize != mIdp.numHotseatIcons || mSrcAllAppsRank != mIdp.hotseatAllAppsRank) {
+ migrateHotseat();
+ }
+
+ if (mShouldRemoveX || mShouldRemoveY) {
+ if ((mSrcY - mTrgX) > 1 || (mSrcY - mSrcY) > 1) {
+ // TODO: support this.
+ throw new Exception("The universe is too large for migration");
+ } else {
+ migrateWorkspace();
+ }
+ }
+
+ // Update items
+ if (!mUpdateOperations.isEmpty()) {
+ mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
+ }
+
+ if (!mEntryToRemove.isEmpty()) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
+ }
+ mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
+ Utilities.createDbSelectionQuery(
+ LauncherSettings.Favorites._ID, mEntryToRemove), null);
+ }
+
+ if (!mUpdateOperations.isEmpty() || !mEntryToRemove.isEmpty()) {
+ // Make sure we haven't removed everything.
+ final Cursor c = mContext.getContentResolver().query(
+ LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
+ boolean hasData = c.moveToNext();
+ c.close();
+ if (!hasData) {
+ throw new Exception("Removed every thing during grid resize");
+ }
+ }
+ }
+
+ /**
+ * To migrate hotseat, we load all the entries in order (LTR or RTL) and arrange them
+ * in the order in the new hotseat while keeping an empty space for all-apps. If the number of
+ * entries is more than what can fit in the new hotseat, we drop the entries with least weight.
+ * For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
+ * & {@see #WT_FOLDER_FACTOR}.
+ */
+ private void migrateHotseat() {
+ ArrayList<DbEntry> items = loadHotseatEntries();
+
+ int requiredCount = mIdp.numHotseatIcons - 1;
+
+ while (items.size() > requiredCount) {
+ // Pick the center item by default.
+ DbEntry toRemove = items.get(items.size() / 2);
+
+ // Find the item with least weight.
+ for (DbEntry entry : items) {
+ if (entry.weight < toRemove.weight) {
+ toRemove = entry;
+ }
+ }
+
+ mEntryToRemove.add(toRemove.id);
+ items.remove(toRemove);
+ }
+
+ // Update screen IDS
+ int newScreenId = 0;
+ for (DbEntry entry : items) {
+ if (entry.screenId != newScreenId) {
+ entry.screenId = newScreenId;
+
+ // These values does not affect the item position, but we should set them
+ // to something other than -1.
+ entry.cellX = newScreenId;
+ entry.cellY = 0;
+
+ update(entry);
+ }
+
+ newScreenId++;
+ if (newScreenId == mIdp.hotseatAllAppsRank) {
+ newScreenId++;
+ }
+ }
+ }
+
+ private void migrateWorkspace() throws Exception {
+ mCarryOver = new ArrayList<>();
+
ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
@@ -157,27 +268,6 @@
LauncherAppState.getInstance().getModel()
.updateWorkspaceScreenOrder(mContext, allScreens);
}
-
- // Update items
- mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
-
- if (!mEntryToRemove.isEmpty()) {
- if (DEBUG) {
- Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
- }
- mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
- Utilities.createDbSelectionQuery(
- LauncherSettings.Favorites._ID, mEntryToRemove), null);
- }
-
- // Make sure we haven't removed everything.
- final Cursor c = mContext.getContentResolver().query(
- LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
- boolean hasData = c.moveToNext();
- c.close();
- if (!hasData) {
- throw new Exception("Removed every thing during grid resize");
- }
}
/**
@@ -191,7 +281,7 @@
* (otherwise they are placed on a new screen).
*/
private void migrateScreen(long screenId) {
- ArrayList<DbEntry> items = loadEntries(screenId);
+ ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
int removedCol = Integer.MAX_VALUE;
int removedRow = Integer.MAX_VALUE;
@@ -329,7 +419,7 @@
return finalItems;
}
- @Thunk void markCells(boolean[][] occupied, DbEntry item, boolean val) {
+ private void markCells(boolean[][] occupied, DbEntry item, boolean val) {
for (int i = item.cellX; i < (item.cellX + item.spanX); i++) {
for (int j = item.cellY; j < (item.cellY + item.spanY); j++) {
occupied[i][j] = val;
@@ -337,7 +427,7 @@
}
}
- @Thunk boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
+ private boolean isVacant(boolean[][] occupied, int x, int y, int w, int h) {
if (x + w > mTrgX) return false;
if (y + h > mTrgY) return false;
@@ -545,10 +635,71 @@
}
}
+ private ArrayList<DbEntry> loadHotseatEntries() {
+ Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+ new String[]{
+ Favorites._ID, // 0
+ Favorites.ITEM_TYPE, // 1
+ Favorites.INTENT, // 2
+ Favorites.SCREEN}, // 3
+ Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT, null, null, null);
+
+ final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
+ final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
+ final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
+ final int indexScreen = c.getColumnIndexOrThrow(Favorites.SCREEN);
+
+ ArrayList<DbEntry> entries = new ArrayList<>();
+ while (c.moveToNext()) {
+ DbEntry entry = new DbEntry();
+ entry.id = c.getLong(indexId);
+ entry.itemType = c.getInt(indexItemType);
+ entry.screenId = c.getLong(indexScreen);
+
+ if (entry.screenId >= mSrcHotseatSize) {
+ mEntryToRemove.add(entry.id);
+ continue;
+ }
+
+ try {
+ // calculate weight
+ switch (entry.itemType) {
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_APPLICATION: {
+ verifyIntent(c.getString(indexIntent));
+ entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
+ ? WT_SHORTCUT : WT_APPLICATION;
+ break;
+ }
+ case Favorites.ITEM_TYPE_FOLDER: {
+ int total = getFolderItemsCount(entry.id);
+ if (total == 0) {
+ throw new Exception("Folder is empty");
+ }
+ entry.weight = WT_FOLDER_FACTOR * total;
+ break;
+ }
+ default:
+ throw new Exception("Invalid item type");
+ }
+ } catch (Exception e) {
+ if (DEBUG) {
+ Log.d(TAG, "Removing item " + entry.id, e);
+ }
+ mEntryToRemove.add(entry.id);
+ continue;
+ }
+ entries.add(entry);
+ }
+ c.close();
+ return entries;
+ }
+
+
/**
* Loads entries for a particular screen id.
*/
- public ArrayList<DbEntry> loadEntries(long screen) {
+ private ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
new String[] {
Favorites._ID, // 0
@@ -733,7 +884,7 @@
}
}
- @Thunk static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
+ private static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
for (DbEntry e : src) {
dup.add(e.copy());
@@ -749,18 +900,39 @@
public static void markForMigration(Context context, int srcX, int srcY,
HashSet<String> widgets) {
Utilities.getPrefs(context).edit()
- .putString(KEY_MIGRATION_SOURCE_SIZE, srcX + "," + srcY)
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(srcX, srcY))
.putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
.apply();
}
public static boolean shouldRunTask(Context context) {
- return !TextUtils.isEmpty(Utilities.getPrefs(context).getString(KEY_MIGRATION_SOURCE_SIZE, ""));
+ SharedPreferences prefs = Utilities.getPrefs(context);
+ InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+
+ // Run task if workspace or hotseat size has changed.
+ return !getPointString(idp.numColumns, idp.numRows).equals(
+ prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+ || !getPointString(idp.numHotseatIcons, idp.hotseatAllAppsRank).equals(
+ prefs.getString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, ""));
}
public static void clearFlags(Context context) {
- Utilities.getPrefs(context).edit().remove(KEY_MIGRATION_SOURCE_SIZE)
- .remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+ Utilities.getPrefs(context).edit().remove(KEY_MIGRATION_WIDGET_MINSIZE).commit();
+ }
+
+ public static void saveCurrentConfig(Context context) {
+ InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
+ Utilities.getPrefs(context).edit()
+ .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE,
+ getPointString(idp.numColumns, idp.numRows))
+ .putString(KEY_MIGRATION_SRC_HOTSEAT_SIZE,
+ getPointString(idp.numHotseatIcons, idp.hotseatAllAppsRank))
+ .remove(KEY_MIGRATION_WIDGET_MINSIZE)
+ .commit();
+ }
+
+ private static String getPointString(int x, int y) {
+ return String.format(Locale.ENGLISH, "%d,%d", x, y);
}
}
diff --git a/src/com/android/launcher3/util/ConfigMonitor.java b/src/com/android/launcher3/util/ConfigMonitor.java
index c61fa88..bdb1639 100644
--- a/src/com/android/launcher3/util/ConfigMonitor.java
+++ b/src/com/android/launcher3/util/ConfigMonitor.java
@@ -23,26 +23,30 @@
import android.content.res.Configuration;
import android.util.Log;
+import com.android.launcher3.Utilities;
+
/**
* {@link BroadcastReceiver} which watches configuration changes and
- * restarts the process in case changes which affect the device profile.
+ * restarts the process in case changes which affect the device profile occur.
*/
public class ConfigMonitor extends BroadcastReceiver {
private final Context mContext;
private final float mFontScale;
+ private final int mDensity;
public ConfigMonitor(Context context) {
mContext = context;
Configuration config = context.getResources().getConfiguration();
mFontScale = config.fontScale;
+ mDensity = getDensity(config);
}
@Override
public void onReceive(Context context, Intent intent) {
Configuration config = context.getResources().getConfiguration();
- if (mFontScale != config.fontScale) {
+ if (mFontScale != config.fontScale || mDensity != getDensity(config)) {
Log.d("ConfigMonitor", "Configuration changed, restarting launcher");
mContext.unregisterReceiver(this);
android.os.Process.killProcess(android.os.Process.myPid());
@@ -52,4 +56,8 @@
public void register() {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
}
+
+ private static int getDensity(Configuration config) {
+ return Utilities.ATLEAST_JB_MR1 ? config.densityDpi : 0;
+ }
}