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
This commit is contained in:
Sunny Goyal
2015-12-14 14:27:38 -08:00
parent 52279f3bc3
commit f862a26347
6 changed files with 240 additions and 57 deletions
@@ -73,12 +73,12 @@ public class InvariantDeviceProfile {
/**
* 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;
@@ -24,7 +24,7 @@ import android.database.Cursor;
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 @@ public class LauncherBackupAgentHelper extends BackupAgentHelper {
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);
@@ -52,7 +52,7 @@ import com.android.launcher3.backup.BackupProtos.Screen;
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 @@ public class LauncherBackupHelper implements BackupHelper {
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.
+6 -4
View File
@@ -57,7 +57,7 @@ import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
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 @@ public class LauncherModel extends BroadcastReceiver
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 @@ public class LauncherModel extends BroadcastReceiver
}
Log.v(TAG, "Workspace migration completed in "
+ (System.currentTimeMillis() - migrationStartTime));
GridSizeMigrationTask.saveCurrentConfig(mContext);
}
if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) {
@@ -24,29 +24,33 @@ import com.android.launcher3.Utilities;
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 @@ public class MigrateFromRestoreTask {
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 @@ public class MigrateFromRestoreTask {
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 @@ public class MigrateFromRestoreTask {
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 @@ public class MigrateFromRestoreTask {
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 @@ public class MigrateFromRestoreTask {
* (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 @@ public class MigrateFromRestoreTask {
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 @@ public class MigrateFromRestoreTask {
}
}
@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 @@ public class MigrateFromRestoreTask {
}
}
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 @@ public class MigrateFromRestoreTask {
}
}
@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 class MigrateFromRestoreTask {
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);
}
}
@@ -23,26 +23,30 @@ import android.content.IntentFilter;
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 class ConfigMonitor extends BroadcastReceiver {
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;
}
}