blob: 8762fef74df5a4ffba76a71415d64aa005791f5f [file] [log] [blame]
Sunny Goyale5bb7052015-07-27 14:36:07 -07001package com.android.launcher3.model;
2
3import android.content.ComponentName;
4import android.content.ContentProviderOperation;
5import android.content.ContentValues;
6import android.content.Context;
7import android.content.Intent;
8import android.content.SharedPreferences;
9import android.content.pm.PackageInfo;
Sunny Goyala9e2f5a2016-06-10 12:22:04 -070010import android.content.pm.PackageManager;
Sunny Goyale5bb7052015-07-27 14:36:07 -070011import android.database.Cursor;
12import android.graphics.Point;
Sunny Goyalf076eae2016-01-11 12:25:10 -080013import android.net.Uri;
Sunny Goyale5bb7052015-07-27 14:36:07 -070014import android.text.TextUtils;
15import android.util.Log;
16
17import com.android.launcher3.InvariantDeviceProfile;
18import com.android.launcher3.ItemInfo;
19import com.android.launcher3.LauncherAppState;
20import com.android.launcher3.LauncherAppWidgetProviderInfo;
21import com.android.launcher3.LauncherModel;
22import com.android.launcher3.LauncherProvider;
23import com.android.launcher3.LauncherSettings;
24import com.android.launcher3.LauncherSettings.Favorites;
25import com.android.launcher3.Utilities;
Sunny Goyalda4fe1a2016-05-26 16:05:17 -070026import com.android.launcher3.Workspace;
Sunny Goyalf076eae2016-01-11 12:25:10 -080027import com.android.launcher3.backup.nano.BackupProtos;
Sunny Goyal2e1efb42016-03-03 16:58:55 -080028import com.android.launcher3.compat.AppWidgetManagerCompat;
Sunny Goyale5bb7052015-07-27 14:36:07 -070029import com.android.launcher3.compat.PackageInstallerCompat;
Sunny Goyala9e2f5a2016-06-10 12:22:04 -070030import com.android.launcher3.config.FeatureFlags;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -070031import com.android.launcher3.util.GridOccupancy;
Sunny Goyale5bb7052015-07-27 14:36:07 -070032import com.android.launcher3.util.LongArrayMap;
Sunny Goyale5bb7052015-07-27 14:36:07 -070033
34import java.util.ArrayList;
35import java.util.Collections;
36import java.util.HashMap;
37import java.util.HashSet;
Sunny Goyalf862a262015-12-14 14:27:38 -080038import java.util.Locale;
Sunny Goyale5bb7052015-07-27 14:36:07 -070039
40/**
41 * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
Sunny Goyalf862a262015-12-14 14:27:38 -080042 * result of restoring from a larger device or device density change.
Sunny Goyale5bb7052015-07-27 14:36:07 -070043 */
Sunny Goyalf862a262015-12-14 14:27:38 -080044public class GridSizeMigrationTask {
Sunny Goyale5bb7052015-07-27 14:36:07 -070045
Sunny Goyalf862a262015-12-14 14:27:38 -080046 public static boolean ENABLED = Utilities.isNycOrAbove();
Sunny Goyal6579e1e2015-08-11 12:10:34 -070047
Sunny Goyalf862a262015-12-14 14:27:38 -080048 private static final String TAG = "GridSizeMigrationTask";
Sunny Goyald934e0b2015-07-31 12:40:57 -070049 private static final boolean DEBUG = true;
Sunny Goyale5bb7052015-07-27 14:36:07 -070050
Sunny Goyalf862a262015-12-14 14:27:38 -080051 private static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
52 private static final String KEY_MIGRATION_SRC_HOTSEAT_SIZE = "migration_src_hotseat_size";
53
54 // Set of entries indicating minimum size a widget can be resized to. This is used during
55 // restore in case the widget has not been installed yet.
Sunny Goyale5bb7052015-07-27 14:36:07 -070056 private static final String KEY_MIGRATION_WIDGET_MINSIZE = "migration_widget_min_size";
57
58 // These are carefully selected weights for various item types (Math.random?), to allow for
Sunny Goyalf862a262015-12-14 14:27:38 -080059 // the least absurd migration experience.
Sunny Goyale5bb7052015-07-27 14:36:07 -070060 private static final float WT_SHORTCUT = 1;
61 private static final float WT_APPLICATION = 0.8f;
62 private static final float WT_WIDGET_MIN = 2;
63 private static final float WT_WIDGET_FACTOR = 0.6f;
64 private static final float WT_FOLDER_FACTOR = 0.5f;
65
66 private final Context mContext;
Sunny Goyale5bb7052015-07-27 14:36:07 -070067 private final InvariantDeviceProfile mIdp;
68
Sunny Goyalf076eae2016-01-11 12:25:10 -080069 private final HashMap<String, Point> mWidgetMinSize = new HashMap<>();
70 private final ContentValues mTempValues = new ContentValues();
Sunny Goyala9e2f5a2016-06-10 12:22:04 -070071 protected final ArrayList<Long> mEntryToRemove = new ArrayList<>();
Sunny Goyalf076eae2016-01-11 12:25:10 -080072 private final ArrayList<ContentProviderOperation> mUpdateOperations = new ArrayList<>();
Sunny Goyala9e2f5a2016-06-10 12:22:04 -070073 protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
Sunny Goyalf076eae2016-01-11 12:25:10 -080074 private final HashSet<String> mValidPackages;
Sunny Goyale5bb7052015-07-27 14:36:07 -070075
76 private final int mSrcX, mSrcY;
Sunny Goyalf862a262015-12-14 14:27:38 -080077 private final int mTrgX, mTrgY;
Sunny Goyale5bb7052015-07-27 14:36:07 -070078 private final boolean mShouldRemoveX, mShouldRemoveY;
79
Sunny Goyalf862a262015-12-14 14:27:38 -080080 private final int mSrcHotseatSize;
81 private final int mSrcAllAppsRank;
Sunny Goyalf076eae2016-01-11 12:25:10 -080082 private final int mDestHotseatSize;
83 private final int mDestAllAppsRank;
Sunny Goyalf862a262015-12-14 14:27:38 -080084
Sunny Goyalf076eae2016-01-11 12:25:10 -080085 protected GridSizeMigrationTask(Context context, InvariantDeviceProfile idp,
86 HashSet<String> validPackages, HashMap<String, Point> widgetMinSize,
87 Point sourceSize, Point targetSize) {
Sunny Goyale5bb7052015-07-27 14:36:07 -070088 mContext = context;
Sunny Goyalf076eae2016-01-11 12:25:10 -080089 mValidPackages = validPackages;
90 mWidgetMinSize.putAll(widgetMinSize);
91 mIdp = idp;
Sunny Goyale5bb7052015-07-27 14:36:07 -070092
Sunny Goyale5bb7052015-07-27 14:36:07 -070093 mSrcX = sourceSize.x;
94 mSrcY = sourceSize.y;
95
Sunny Goyalf076eae2016-01-11 12:25:10 -080096 mTrgX = targetSize.x;
97 mTrgY = targetSize.y;
Sunny Goyale5bb7052015-07-27 14:36:07 -070098
Sunny Goyale5bb7052015-07-27 14:36:07 -070099 mShouldRemoveX = mTrgX < mSrcX;
100 mShouldRemoveY = mTrgY < mSrcY;
Sunny Goyalf076eae2016-01-11 12:25:10 -0800101
102 // Non-used variables
103 mSrcHotseatSize = mSrcAllAppsRank = mDestHotseatSize = mDestAllAppsRank = -1;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700104 }
105
Sunny Goyalf076eae2016-01-11 12:25:10 -0800106 protected GridSizeMigrationTask(Context context,
107 InvariantDeviceProfile idp, HashSet<String> validPackages,
108 int srcHotseatSize, int srcAllAppsRank,
109 int destHotseatSize, int destAllAppsRank) {
110 mContext = context;
111 mIdp = idp;
112 mValidPackages = validPackages;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700113
Sunny Goyalf076eae2016-01-11 12:25:10 -0800114 mSrcHotseatSize = srcHotseatSize;
115 mSrcAllAppsRank = srcAllAppsRank;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700116
Sunny Goyalf076eae2016-01-11 12:25:10 -0800117 mDestHotseatSize = destHotseatSize;
118 mDestAllAppsRank = destAllAppsRank;
Sunny Goyalf862a262015-12-14 14:27:38 -0800119
Sunny Goyalf076eae2016-01-11 12:25:10 -0800120 // Non-used variables
121 mSrcX = mSrcY = mTrgX = mTrgY = -1;
122 mShouldRemoveX = mShouldRemoveY = false;
123 }
Sunny Goyalf862a262015-12-14 14:27:38 -0800124
Sunny Goyalf076eae2016-01-11 12:25:10 -0800125 /**
126 * Applied all the pending DB operations
127 * @return true if any DB operation was commited.
128 */
129 private boolean applyOperations() throws Exception {
Sunny Goyalf862a262015-12-14 14:27:38 -0800130 // Update items
131 if (!mUpdateOperations.isEmpty()) {
132 mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, mUpdateOperations);
133 }
134
135 if (!mEntryToRemove.isEmpty()) {
136 if (DEBUG) {
137 Log.d(TAG, "Removing items: " + TextUtils.join(", ", mEntryToRemove));
138 }
139 mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
140 Utilities.createDbSelectionQuery(
141 LauncherSettings.Favorites._ID, mEntryToRemove), null);
142 }
143
Sunny Goyalf076eae2016-01-11 12:25:10 -0800144 return !mUpdateOperations.isEmpty() || !mEntryToRemove.isEmpty();
Sunny Goyalf862a262015-12-14 14:27:38 -0800145 }
146
147 /**
148 * To migrate hotseat, we load all the entries in order (LTR or RTL) and arrange them
149 * in the order in the new hotseat while keeping an empty space for all-apps. If the number of
150 * entries is more than what can fit in the new hotseat, we drop the entries with least weight.
151 * For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
152 * & {@see #WT_FOLDER_FACTOR}.
Sunny Goyalf076eae2016-01-11 12:25:10 -0800153 * @return true if any DB change was made
Sunny Goyalf862a262015-12-14 14:27:38 -0800154 */
Sunny Goyalf076eae2016-01-11 12:25:10 -0800155 protected boolean migrateHotseat() throws Exception {
Sunny Goyalf862a262015-12-14 14:27:38 -0800156 ArrayList<DbEntry> items = loadHotseatEntries();
157
Sunny Goyalf076eae2016-01-11 12:25:10 -0800158 int requiredCount = mDestHotseatSize - 1;
Sunny Goyalf862a262015-12-14 14:27:38 -0800159
160 while (items.size() > requiredCount) {
161 // Pick the center item by default.
162 DbEntry toRemove = items.get(items.size() / 2);
163
164 // Find the item with least weight.
165 for (DbEntry entry : items) {
166 if (entry.weight < toRemove.weight) {
167 toRemove = entry;
168 }
169 }
170
171 mEntryToRemove.add(toRemove.id);
172 items.remove(toRemove);
173 }
174
175 // Update screen IDS
176 int newScreenId = 0;
177 for (DbEntry entry : items) {
178 if (entry.screenId != newScreenId) {
179 entry.screenId = newScreenId;
180
181 // These values does not affect the item position, but we should set them
182 // to something other than -1.
183 entry.cellX = newScreenId;
184 entry.cellY = 0;
185
186 update(entry);
187 }
188
189 newScreenId++;
Sunny Goyalf076eae2016-01-11 12:25:10 -0800190 if (newScreenId == mDestAllAppsRank) {
Sunny Goyalf862a262015-12-14 14:27:38 -0800191 newScreenId++;
192 }
193 }
Sunny Goyalf076eae2016-01-11 12:25:10 -0800194
195 return applyOperations();
Sunny Goyalf862a262015-12-14 14:27:38 -0800196 }
197
Sunny Goyalf076eae2016-01-11 12:25:10 -0800198 /**
199 * @return true if any DB change was made
200 */
201 protected boolean migrateWorkspace() throws Exception {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700202 ArrayList<Long> allScreens = LauncherModel.loadWorkspaceScreensDb(mContext);
203 if (allScreens.isEmpty()) {
204 throw new Exception("Unable to get workspace screens");
205 }
206
207 for (long screenId : allScreens) {
208 if (DEBUG) {
209 Log.d(TAG, "Migrating " + screenId);
210 }
211 migrateScreen(screenId);
212 }
213
214 if (!mCarryOver.isEmpty()) {
215 LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
216 for (DbEntry e : mCarryOver) {
217 itemMap.put(e.id, e);
218 }
219
220 do {
221 // Some items are still remaining. Try adding a few new screens.
222
223 // At every iteration, make sure that at least one item is removed from
224 // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
225 // break the loop and abort migration by throwing an exception.
226 OptimalPlacementSolution placement = new OptimalPlacementSolution(
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700227 new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), 0, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700228 placement.find();
229 if (placement.finalPlacedItems.size() > 0) {
Sunny Goyald2497482015-09-22 18:24:19 -0700230 long newScreenId = LauncherSettings.Settings.call(
231 mContext.getContentResolver(),
232 LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
233 .getLong(LauncherSettings.Settings.EXTRA_VALUE);
Sunny Goyalf076eae2016-01-11 12:25:10 -0800234
Sunny Goyale5bb7052015-07-27 14:36:07 -0700235 allScreens.add(newScreenId);
236 for (DbEntry item : placement.finalPlacedItems) {
237 if (!mCarryOver.remove(itemMap.get(item.id))) {
238 throw new Exception("Unable to find matching items");
239 }
240 item.screenId = newScreenId;
241 update(item);
242 }
243 } else {
244 throw new Exception("None of the items can be placed on an empty screen");
245 }
246
247 } while (!mCarryOver.isEmpty());
248
Sunny Goyalf076eae2016-01-11 12:25:10 -0800249 // Update screens
250 final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
251 mUpdateOperations.add(ContentProviderOperation.newDelete(uri).build());
252 int count = allScreens.size();
253 for (int i = 0; i < count; i++) {
254 ContentValues v = new ContentValues();
255 long screenId = allScreens.get(i);
256 v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
257 v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
258 mUpdateOperations.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
259 }
Sunny Goyale5bb7052015-07-27 14:36:07 -0700260 }
Sunny Goyalf076eae2016-01-11 12:25:10 -0800261 return applyOperations();
Sunny Goyale5bb7052015-07-27 14:36:07 -0700262 }
263
264 /**
265 * Migrate a particular screen id.
266 * Strategy:
267 * 1) For all possible combinations of row and column, pick the one which causes the least
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700268 * data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
Sunny Goyale5bb7052015-07-27 14:36:07 -0700269 * 2) Maintain a list of all lost items before this screen, and add any new item lost from
270 * this screen to that list as well.
271 * 3) If all those items from the above list can be placed on this screen, place them
272 * (otherwise they are placed on a new screen).
273 */
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700274 protected void migrateScreen(long screenId) {
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700275 // If we are migrating the first screen, do not touch the first row.
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700276 int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
277 ? 1 : 0;
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700278
Sunny Goyalf862a262015-12-14 14:27:38 -0800279 ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700280
281 int removedCol = Integer.MAX_VALUE;
282 int removedRow = Integer.MAX_VALUE;
283
284 // removeWt represents the cost function for loss of items during migration, and moveWt
285 // represents the cost function for repositioning the items. moveWt is only considered if
286 // removeWt is same for two different configurations.
287 // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
288 // cost.
289 float removeWt = Float.MAX_VALUE;
290 float moveWt = Float.MAX_VALUE;
291 float[] outLoss = new float[2];
292 ArrayList<DbEntry> finalItems = null;
293
294 // Try removing all possible combinations
295 for (int x = 0; x < mSrcX; x++) {
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700296 for (int y = startY; y < mSrcY; y++) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700297 // Use a deep copy when trying out a particular combination as it can change
298 // the underlying object.
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700299 ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700300
301 if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1] < moveWt))) {
302 removeWt = outLoss[0];
303 moveWt = outLoss[1];
304 removedCol = mShouldRemoveX ? x : removedCol;
305 removedRow = mShouldRemoveY ? y : removedRow;
306 finalItems = itemsOnScreen;
307 }
308
309 // No need to loop over all rows, if a row removal is not needed.
310 if (!mShouldRemoveY) {
311 break;
312 }
313 }
314
315 if (!mShouldRemoveX) {
316 break;
317 }
318 }
319
320 if (DEBUG) {
321 Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
322 removedRow, removedCol, screenId));
323 }
324
325 LongArrayMap<DbEntry> itemMap = new LongArrayMap<>();
326 for (DbEntry e : deepCopy(items)) {
327 itemMap.put(e.id, e);
328 }
329
330 for (DbEntry item : finalItems) {
331 DbEntry org = itemMap.get(item.id);
332 itemMap.remove(item.id);
333
334 // Check if update is required
335 if (!item.columnsSame(org)) {
336 update(item);
337 }
338 }
339
340 // The remaining items in {@link #itemMap} are those which didn't get placed.
341 for (DbEntry item : itemMap) {
342 mCarryOver.add(item);
343 }
344
345 if (!mCarryOver.isEmpty() && removeWt == 0) {
346 // No new items were removed in this step. Try placing all the items on this screen.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700347 GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700348 occupied.markCells(0, 0, mTrgX, startY, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700349 for (DbEntry item : finalItems) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700350 occupied.markCells(item, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700351 }
352
353 OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700354 deepCopy(mCarryOver), startY, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700355 placement.find();
356 if (placement.lowestWeightLoss == 0) {
357 // All items got placed
358
359 for (DbEntry item : placement.finalPlacedItems) {
360 item.screenId = screenId;
361 update(item);
362 }
363
364 mCarryOver.clear();
365 }
366 }
367 }
368
369 /**
370 * Updates an item in the DB.
371 */
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700372 protected void update(DbEntry item) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700373 mTempValues.clear();
374 item.addToContentValues(mTempValues);
375 mUpdateOperations.add(ContentProviderOperation
376 .newUpdate(LauncherSettings.Favorites.getContentUri(item.id))
377 .withValues(mTempValues).build());
378 }
379
380 /**
381 * Tries the remove the provided row and column.
382 * @param items all the items on the screen under operation
383 * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
384 * with the overall item movement.
385 */
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700386 private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
387 ArrayList<DbEntry> items, float[] outLoss) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700388 GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700389 occupied.markCells(0, 0, mTrgX, startY, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700390
391 col = mShouldRemoveX ? col : Integer.MAX_VALUE;
392 row = mShouldRemoveY ? row : Integer.MAX_VALUE;
393
394 ArrayList<DbEntry> finalItems = new ArrayList<>();
395 ArrayList<DbEntry> removedItems = new ArrayList<>();
396
397 for (DbEntry item : items) {
398 if ((item.cellX <= col && (item.spanX + item.cellX) > col)
399 || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
400 removedItems.add(item);
401 if (item.cellX >= col) item.cellX --;
402 if (item.cellY >= row) item.cellY --;
403 } else {
404 if (item.cellX > col) item.cellX --;
405 if (item.cellY > row) item.cellY --;
406 finalItems.add(item);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700407 occupied.markCells(item, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700408 }
409 }
410
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700411 OptimalPlacementSolution placement =
412 new OptimalPlacementSolution(occupied, removedItems, startY);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700413 placement.find();
414 finalItems.addAll(placement.finalPlacedItems);
415 outLoss[0] = placement.lowestWeightLoss;
416 outLoss[1] = placement.lowestMoveCost;
417 return finalItems;
418 }
419
Sunny Goyale5bb7052015-07-27 14:36:07 -0700420 private class OptimalPlacementSolution {
421 private final ArrayList<DbEntry> itemsToPlace;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700422 private final GridOccupancy occupied;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700423
424 // If set to true, item movement are not considered in move cost, leading to a more
425 // linear placement.
426 private final boolean ignoreMove;
427
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700428 // The first row in the grid from where the placement should start.
429 private final int startY;
430
Sunny Goyale5bb7052015-07-27 14:36:07 -0700431 float lowestWeightLoss = Float.MAX_VALUE;
432 float lowestMoveCost = Float.MAX_VALUE;
433 ArrayList<DbEntry> finalPlacedItems;
434
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700435 public OptimalPlacementSolution(
436 GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace, int startY) {
437 this(occupied, itemsToPlace, startY, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700438 }
439
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700440 public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace,
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700441 int startY, boolean ignoreMove) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700442 this.occupied = occupied;
443 this.itemsToPlace = itemsToPlace;
444 this.ignoreMove = ignoreMove;
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700445 this.startY = startY;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700446
447 // Sort the items such that larger widgets appear first followed by 1x1 items
448 Collections.sort(this.itemsToPlace);
449 }
450
451 public void find() {
452 find(0, 0, 0, new ArrayList<DbEntry>());
453 }
454
455 /**
456 * Recursively finds a placement for the provided items.
457 * @param index the position in {@link #itemsToPlace} to start looking at.
458 * @param weightLoss total weight loss upto this point
459 * @param moveCost total move cost upto this point
460 * @param itemsPlaced all the items already placed upto this point
461 */
462 public void find(int index, float weightLoss, float moveCost,
463 ArrayList<DbEntry> itemsPlaced) {
464 if ((weightLoss >= lowestWeightLoss) ||
465 ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
466 // Abort, as we already have a better solution.
467 return;
468
469 } else if (index >= itemsToPlace.size()) {
470 // End loop.
471 lowestWeightLoss = weightLoss;
472 lowestMoveCost = moveCost;
473
474 // Keep a deep copy of current configuration as it can change during recursion.
475 finalPlacedItems = deepCopy(itemsPlaced);
476 return;
477 }
478
479 DbEntry me = itemsToPlace.get(index);
480 int myX = me.cellX;
481 int myY = me.cellY;
482
483 // List of items to pass over if this item was placed.
484 ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
485 itemsIncludingMe.addAll(itemsPlaced);
486 itemsIncludingMe.add(me);
487
488 if (me.spanX > 1 || me.spanY > 1) {
489 // If the current item is a widget (and it greater than 1x1), try to place it at
490 // all possible positions. This is because a widget placed at one position can
491 // affect the placement of a different widget.
492 int myW = me.spanX;
493 int myH = me.spanY;
494
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700495 for (int y = startY; y < mTrgY; y++) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700496 for (int x = 0; x < mTrgX; x++) {
497 float newMoveCost = moveCost;
498 if (x != myX) {
499 me.cellX = x;
500 newMoveCost ++;
501 }
502 if (y != myY) {
503 me.cellY = y;
504 newMoveCost ++;
505 }
506 if (ignoreMove) {
507 newMoveCost = moveCost;
508 }
509
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700510 if (occupied.isRegionVacant(x, y, myW, myH)) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700511 // place at this position and continue search.
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700512 occupied.markCells(me, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700513 find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700514 occupied.markCells(me, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700515 }
516
517 // Try resizing horizontally
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700518 if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700519 me.spanX --;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700520 occupied.markCells(me, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700521 // 1 extra move cost
522 find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700523 occupied.markCells(me, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700524 me.spanX ++;
525 }
526
527 // Try resizing vertically
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700528 if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700529 me.spanY --;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700530 occupied.markCells(me, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700531 // 1 extra move cost
532 find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700533 occupied.markCells(me, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700534 me.spanY ++;
535 }
536
537 // Try resizing horizontally & vertically
538 if (myH > me.minSpanY && myW > me.minSpanX &&
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700539 occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700540 me.spanX --;
541 me.spanY --;
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700542 occupied.markCells(me, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700543 // 2 extra move cost
544 find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700545 occupied.markCells(me, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700546 me.spanX ++;
547 me.spanY ++;
548 }
549 me.cellX = myX;
550 me.cellY = myY;
551 }
552 }
553
554 // Finally also try a solution when this item is not included. Trying it in the end
555 // causes it to get skipped in most cases due to higher weight loss, and prevents
556 // unnecessary deep copies of various configurations.
557 find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
558 } else {
559 // Since this is a 1x1 item and all the following items are also 1x1, just place
560 // it at 'the most appropriate position' and hope for the best.
561 // The most appropriate position: one with lease straight line distance
562 int newDistance = Integer.MAX_VALUE;
563 int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
564
Sunny Goyalda4fe1a2016-05-26 16:05:17 -0700565 for (int y = startY; y < mTrgY; y++) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700566 for (int x = 0; x < mTrgX; x++) {
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700567 if (!occupied.cells[x][y]) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700568 int dist = ignoreMove ? 0 :
569 ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY - y));
570 if (dist < newDistance) {
571 newX = x;
572 newY = y;
573 newDistance = dist;
574 }
575 }
576 }
577 }
578
579 if (newX < mTrgX && newY < mTrgY) {
580 float newMoveCost = moveCost;
581 if (newX != myX) {
582 me.cellX = newX;
583 newMoveCost ++;
584 }
585 if (newY != myY) {
586 me.cellY = newY;
587 newMoveCost ++;
588 }
589 if (ignoreMove) {
590 newMoveCost = moveCost;
591 }
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700592 occupied.markCells(me, true);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700593 find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
Sunny Goyalff4ba2d2016-04-02 14:12:34 -0700594 occupied.markCells(me, false);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700595 me.cellX = myX;
596 me.cellY = myY;
597
598 // Try to find a solution without this item, only if
599 // 1) there was at least one space, i.e., we were able to place this item
600 // 2) if the next item has the same weight (all items are already sorted), as
601 // if it has lower weight, that solution will automatically get discarded.
602 // 3) ignoreMove false otherwise, move cost is ignored and the weight will
603 // anyway be same.
604 if (index + 1 < itemsToPlace.size()
605 && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
606 find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
607 }
608 } else {
609 // No more space. Jump to the end.
610 for (int i = index + 1; i < itemsToPlace.size(); i++) {
611 weightLoss += itemsToPlace.get(i).weight;
612 }
613 find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
614 }
615 }
616 }
617 }
618
Sunny Goyalf862a262015-12-14 14:27:38 -0800619 private ArrayList<DbEntry> loadHotseatEntries() {
620 Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
621 new String[]{
622 Favorites._ID, // 0
623 Favorites.ITEM_TYPE, // 1
624 Favorites.INTENT, // 2
625 Favorites.SCREEN}, // 3
626 Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT, null, null, null);
627
628 final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
629 final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
630 final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
631 final int indexScreen = c.getColumnIndexOrThrow(Favorites.SCREEN);
632
633 ArrayList<DbEntry> entries = new ArrayList<>();
634 while (c.moveToNext()) {
635 DbEntry entry = new DbEntry();
636 entry.id = c.getLong(indexId);
637 entry.itemType = c.getInt(indexItemType);
638 entry.screenId = c.getLong(indexScreen);
639
640 if (entry.screenId >= mSrcHotseatSize) {
641 mEntryToRemove.add(entry.id);
642 continue;
643 }
644
645 try {
646 // calculate weight
647 switch (entry.itemType) {
648 case Favorites.ITEM_TYPE_SHORTCUT:
649 case Favorites.ITEM_TYPE_APPLICATION: {
650 verifyIntent(c.getString(indexIntent));
651 entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
652 ? WT_SHORTCUT : WT_APPLICATION;
653 break;
654 }
655 case Favorites.ITEM_TYPE_FOLDER: {
656 int total = getFolderItemsCount(entry.id);
657 if (total == 0) {
658 throw new Exception("Folder is empty");
659 }
660 entry.weight = WT_FOLDER_FACTOR * total;
661 break;
662 }
663 default:
664 throw new Exception("Invalid item type");
665 }
666 } catch (Exception e) {
667 if (DEBUG) {
668 Log.d(TAG, "Removing item " + entry.id, e);
669 }
670 mEntryToRemove.add(entry.id);
671 continue;
672 }
673 entries.add(entry);
674 }
675 c.close();
676 return entries;
677 }
678
679
Sunny Goyale5bb7052015-07-27 14:36:07 -0700680 /**
681 * Loads entries for a particular screen id.
682 */
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700683 protected ArrayList<DbEntry> loadWorkspaceEntries(long screen) {
684 Cursor c = queryWorkspace(
Sunny Goyalf076eae2016-01-11 12:25:10 -0800685 new String[]{
686 Favorites._ID, // 0
687 Favorites.ITEM_TYPE, // 1
688 Favorites.CELLX, // 2
689 Favorites.CELLY, // 3
690 Favorites.SPANX, // 4
691 Favorites.SPANY, // 5
692 Favorites.INTENT, // 6
Sunny Goyal2e1efb42016-03-03 16:58:55 -0800693 Favorites.APPWIDGET_PROVIDER, // 7
694 Favorites.APPWIDGET_ID}, // 8
Sunny Goyale5bb7052015-07-27 14:36:07 -0700695 Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700696 + " AND " + Favorites.SCREEN + " = " + screen);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700697
Sunny Goyalf076eae2016-01-11 12:25:10 -0800698 final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
699 final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
700 final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
701 final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
702 final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
703 final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
704 final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
705 final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
Sunny Goyal2e1efb42016-03-03 16:58:55 -0800706 final int indexAppWidgetId = c.getColumnIndexOrThrow(Favorites.APPWIDGET_ID);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700707
Sunny Goyalf076eae2016-01-11 12:25:10 -0800708 ArrayList<DbEntry> entries = new ArrayList<>();
709 while (c.moveToNext()) {
710 DbEntry entry = new DbEntry();
711 entry.id = c.getLong(indexId);
712 entry.itemType = c.getInt(indexItemType);
713 entry.cellX = c.getInt(indexCellX);
714 entry.cellY = c.getInt(indexCellY);
715 entry.spanX = c.getInt(indexSpanX);
716 entry.spanY = c.getInt(indexSpanY);
717 entry.screenId = screen;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700718
Sunny Goyalf076eae2016-01-11 12:25:10 -0800719 try {
720 // calculate weight
721 switch (entry.itemType) {
722 case Favorites.ITEM_TYPE_SHORTCUT:
723 case Favorites.ITEM_TYPE_APPLICATION: {
724 verifyIntent(c.getString(indexIntent));
725 entry.weight = entry.itemType == Favorites.ITEM_TYPE_SHORTCUT
726 ? WT_SHORTCUT : WT_APPLICATION;
727 break;
728 }
729 case Favorites.ITEM_TYPE_APPWIDGET: {
730 String provider = c.getString(indexAppWidgetProvider);
731 ComponentName cn = ComponentName.unflattenFromString(provider);
732 verifyPackage(cn.getPackageName());
733 entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
734 * entry.spanX * entry.spanY);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700735
Sunny Goyal2e1efb42016-03-03 16:58:55 -0800736 int widgetId = c.getInt(indexAppWidgetId);
737 LauncherAppWidgetProviderInfo pInfo = AppWidgetManagerCompat.getInstance(
738 mContext).getLauncherAppWidgetInfo(widgetId);
Sunny Goyalf076eae2016-01-11 12:25:10 -0800739 Point spans = pInfo == null ?
740 mWidgetMinSize.get(provider) : pInfo.getMinSpans(mIdp, mContext);
741 if (spans != null) {
742 entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
743 entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
744 } else {
745 // Assume that the widget be resized down to 2x2
746 entry.minSpanX = entry.minSpanY = 2;
747 }
Sunny Goyale5bb7052015-07-27 14:36:07 -0700748
Sunny Goyalf076eae2016-01-11 12:25:10 -0800749 if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
750 throw new Exception("Widget can't be resized down to fit the grid");
751 }
752 break;
753 }
754 case Favorites.ITEM_TYPE_FOLDER: {
755 int total = getFolderItemsCount(entry.id);
756 if (total == 0) {
757 throw new Exception("Folder is empty");
758 }
759 entry.weight = WT_FOLDER_FACTOR * total;
760 break;
761 }
762 default:
763 throw new Exception("Invalid item type");
764 }
765 } catch (Exception e) {
766 if (DEBUG) {
767 Log.d(TAG, "Removing item " + entry.id, e);
768 }
769 mEntryToRemove.add(entry.id);
770 continue;
771 }
772 entries.add(entry);
773 }
774 c.close();
775 return entries;
Sunny Goyale5bb7052015-07-27 14:36:07 -0700776 }
777
778 /**
779 * @return the number of valid items in the folder.
780 */
781 private int getFolderItemsCount(long folderId) {
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700782 Cursor c = queryWorkspace(
Sunny Goyalf076eae2016-01-11 12:25:10 -0800783 new String[]{Favorites._ID, Favorites.INTENT},
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700784 Favorites.CONTAINER + " = " + folderId);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700785
786 int total = 0;
787 while (c.moveToNext()) {
788 try {
789 verifyIntent(c.getString(1));
790 total++;
791 } catch (Exception e) {
792 mEntryToRemove.add(c.getLong(0));
793 }
794 }
Tony Wickhamfb062c62015-10-16 10:12:23 -0700795 c.close();
Sunny Goyale5bb7052015-07-27 14:36:07 -0700796 return total;
797 }
798
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700799 protected Cursor queryWorkspace(String[] columns, String where) {
800 return mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
801 columns, where, null, null, null);
802 }
803
Sunny Goyale5bb7052015-07-27 14:36:07 -0700804 /**
805 * Verifies if the intent should be restored.
806 */
807 private void verifyIntent(String intentStr) throws Exception {
808 Intent intent = Intent.parseUri(intentStr, 0);
809 if (intent.getComponent() != null) {
810 verifyPackage(intent.getComponent().getPackageName());
811 } else if (intent.getPackage() != null) {
812 // Only verify package if the component was null.
813 verifyPackage(intent.getPackage());
814 }
815 }
816
817 /**
818 * Verifies if the package should be restored
819 */
820 private void verifyPackage(String packageName) throws Exception {
821 if (!mValidPackages.contains(packageName)) {
822 throw new Exception("Package not available");
823 }
824 }
825
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700826 protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700827
828 public float weight;
829
830 public DbEntry() { }
831
832 public DbEntry copy() {
833 DbEntry entry = new DbEntry();
834 entry.copyFrom(this);
835 entry.weight = weight;
836 entry.minSpanX = minSpanX;
837 entry.minSpanY = minSpanY;
838 return entry;
839 }
840
841 /**
842 * Comparator such that larger widgets come first, followed by all 1x1 items
843 * based on their weights.
844 */
845 @Override
846 public int compareTo(DbEntry another) {
847 if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
848 if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
849 return another.spanY * another.spanX - spanX * spanY;
850 } else {
851 return -1;
852 }
853 } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
854 return 1;
855 } else {
856 // Place higher weight before lower weight.
857 return Float.compare(another.weight, weight);
858 }
859 }
860
861 public boolean columnsSame(DbEntry org) {
862 return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
863 org.spanY == spanY && org.screenId == screenId;
864 }
865
866 public void addToContentValues(ContentValues values) {
867 values.put(LauncherSettings.Favorites.SCREEN, screenId);
868 values.put(LauncherSettings.Favorites.CELLX, cellX);
869 values.put(LauncherSettings.Favorites.CELLY, cellY);
870 values.put(LauncherSettings.Favorites.SPANX, spanX);
871 values.put(LauncherSettings.Favorites.SPANY, spanY);
872 }
873 }
874
Sunny Goyalf862a262015-12-14 14:27:38 -0800875 private static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
Sunny Goyale5bb7052015-07-27 14:36:07 -0700876 ArrayList<DbEntry> dup = new ArrayList<DbEntry>(src.size());
877 for (DbEntry e : src) {
878 dup.add(e.copy());
879 }
880 return dup;
881 }
882
883 private static Point parsePoint(String point) {
884 String[] split = point.split(",");
885 return new Point(Integer.parseInt(split[0]), Integer.parseInt(split[1]));
886 }
887
Sunny Goyalf862a262015-12-14 14:27:38 -0800888 private static String getPointString(int x, int y) {
889 return String.format(Locale.ENGLISH, "%d,%d", x, y);
Sunny Goyale5bb7052015-07-27 14:36:07 -0700890 }
891
Sunny Goyalf076eae2016-01-11 12:25:10 -0800892 public static void markForMigration(
893 Context context, HashSet<String> widgets, BackupProtos.DeviceProfieData srcProfile) {
894 Utilities.getPrefs(context).edit()
895 .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE,
896 getPointString((int) srcProfile.desktopCols, (int) srcProfile.desktopRows))
897 .putString(KEY_MIGRATION_SRC_HOTSEAT_SIZE,
898 getPointString((int) srcProfile.hotseatCount, srcProfile.allappsRank))
899 .putStringSet(KEY_MIGRATION_WIDGET_MINSIZE, widgets)
900 .apply();
901 }
902
903 /**
904 * Migrates the workspace and hotseat in case their sizes changed.
905 * @return false if the migration failed.
906 */
907 public static boolean migrateGridIfNeeded(Context context) {
908 SharedPreferences prefs = Utilities.getPrefs(context);
909 InvariantDeviceProfile idp = LauncherAppState.getInstance().getInvariantDeviceProfile();
910
911 String gridSizeString = getPointString(idp.numColumns, idp.numRows);
912 String hotseatSizeString = getPointString(idp.numHotseatIcons, idp.hotseatAllAppsRank);
913
914 if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
915 hotseatSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, ""))) {
916 // Skip if workspace and hotseat sizes have not changed.
917 return true;
918 }
919
920 long migrationStartTime = System.currentTimeMillis();
921 try {
922 boolean dbChanged = false;
923
Sunny Goyala9e2f5a2016-06-10 12:22:04 -0700924 HashSet validPackages = getValidPackages(context);
Sunny Goyalf076eae2016-01-11 12:25:10 -0800925 // Hotseat
926 Point srcHotseatSize = parsePoint(prefs.getString(
927 KEY_MIGRATION_SRC_HOTSEAT_SIZE, hotseatSizeString));
928 if (srcHotseatSize.x != idp.numHotseatIcons ||
929 srcHotseatSize.y != idp.hotseatAllAppsRank) {
930 // Migrate hotseat.
931
932 dbChanged = new GridSizeMigrationTask(context,
933 LauncherAppState.getInstance().getInvariantDeviceProfile(),
934 validPackages,
935 srcHotseatSize.x, srcHotseatSize.y,
936 idp.numHotseatIcons, idp.hotseatAllAppsRank).migrateHotseat();
937 }
938
939 // Grid size
940 Point targetSize = new Point(idp.numColumns, idp.numRows);
941 Point sourceSize = parsePoint(prefs.getString(
942 KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
943
944 if (!targetSize.equals(sourceSize)) {
945
946 // The following list defines all possible grid sizes (and intermediate steps
947 // during migration). Note that at each step, dx <= 1 && dy <= 1. Any grid size
948 // which is not in this list is not migrated.
Sunny Goyal1ab22632016-04-05 16:38:39 -0700949 // Note that the InvariantDeviceProfile defines (rows, cols) but the Points
950 // specified here are defined as (cols, rows).
Sunny Goyalf076eae2016-01-11 12:25:10 -0800951 ArrayList<Point> gridSizeSteps = new ArrayList<>();
Sunny Goyal1ab22632016-04-05 16:38:39 -0700952 gridSizeSteps.add(new Point(3, 2));
Sunny Goyalf076eae2016-01-11 12:25:10 -0800953 gridSizeSteps.add(new Point(3, 3));
Sunny Goyal1ab22632016-04-05 16:38:39 -0700954 gridSizeSteps.add(new Point(4, 3));
Sunny Goyalf076eae2016-01-11 12:25:10 -0800955 gridSizeSteps.add(new Point(4, 4));
956 gridSizeSteps.add(new Point(5, 5));
Sunny Goyal1ab22632016-04-05 16:38:39 -0700957 gridSizeSteps.add(new Point(6, 5));
Sunny Goyalf076eae2016-01-11 12:25:10 -0800958 gridSizeSteps.add(new Point(6, 6));
959 gridSizeSteps.add(new Point(7, 7));
960
961 int sourceSizeIndex = gridSizeSteps.indexOf(sourceSize);
962 int targetSizeIndex = gridSizeSteps.indexOf(targetSize);
963
964 if (sourceSizeIndex <= -1 || targetSizeIndex <= -1) {
965 throw new Exception("Unable to migrate grid size from " + sourceSize
966 + " to " + targetSize);
967 }
968
969 // Min widget sizes
970 HashMap<String, Point> widgetMinSize = new HashMap<>();
971 for (String s : Utilities.getPrefs(context).getStringSet(KEY_MIGRATION_WIDGET_MINSIZE,
972 Collections.<String>emptySet())) {
973 String[] parts = s.split("#");
974 widgetMinSize.put(parts[0], parsePoint(parts[1]));
975 }
976
977 // Migrate the workspace grid, step by step.
978 while (targetSizeIndex < sourceSizeIndex ) {
979 // We only need to migrate the grid if source size is greater
980 // than the target size.
981 Point stepTargetSize = gridSizeSteps.get(sourceSizeIndex - 1);
982 Point stepSourceSize = gridSizeSteps.get(sourceSizeIndex);
983
984 if (new GridSizeMigrationTask(context,
985 LauncherAppState.getInstance().getInvariantDeviceProfile(),
986 validPackages, widgetMinSize,
987 stepSourceSize, stepTargetSize).migrateWorkspace()) {
988 dbChanged = true;
989 }
990 sourceSizeIndex--;
991 }
992 }
993
994 if (dbChanged) {
995 // Make sure we haven't removed everything.
996 final Cursor c = context.getContentResolver().query(
997 LauncherSettings.Favorites.CONTENT_URI, null, null, null, null);
998 boolean hasData = c.moveToNext();
999 c.close();
1000 if (!hasData) {
1001 throw new Exception("Removed every thing during grid resize");
1002 }
1003 }
1004
1005 return true;
1006 } catch (Exception e) {
1007 Log.e(TAG, "Error during grid migration", e);
1008
1009 return false;
1010 } finally {
1011 Log.v(TAG, "Workspace migration completed in "
1012 + (System.currentTimeMillis() - migrationStartTime));
1013
1014 // Save current configuration, so that the migration does not run again.
1015 prefs.edit()
1016 .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
1017 .putString(KEY_MIGRATION_SRC_HOTSEAT_SIZE, hotseatSizeString)
1018 .remove(KEY_MIGRATION_WIDGET_MINSIZE)
1019 .apply();
1020 }
1021 }
Sunny Goyala9e2f5a2016-06-10 12:22:04 -07001022
1023 protected static HashSet<String> getValidPackages(Context context) {
1024 // Initialize list of valid packages. This contain all the packages which are already on
1025 // the device and packages which are being installed. Any item which doesn't belong to
1026 // this set is removed.
1027 // Since the loader removes such items anyway, removing these items here doesn't cause
1028 // any extra data loss and gives us more free space on the grid for better migration.
1029 HashSet validPackages = new HashSet<>();
1030 for (PackageInfo info : context.getPackageManager()
1031 .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
1032 validPackages.add(info.packageName);
1033 }
1034 validPackages.addAll(PackageInstallerCompat.getInstance(context)
1035 .updateAndGetActiveSessionCache().keySet());
1036 return validPackages;
1037 }
Sunny Goyale5bb7052015-07-27 14:36:07 -07001038}