From d502404a44fb7c4ea739622d7f8bdd2a764d97a1 Mon Sep 17 00:00:00 2001 From: Dan Sandler Date: Thu, 9 Jan 2014 15:01:33 -0500 Subject: New launcher2 icon migration algorithm. The user will be able to request "icon migration", which is not a direct mapping of the old workspace, but rather follows this heuristic for bringing the user's favorite icons (by dint of their existence on the workspace) into Launcher3: Workspace shortcuts are placed in lexicographic order on the workspace starting at screen 0 (leaving the bottom row of screen 0 empty to make sure there's room to move things around). Folders are preserved and their contents sorted. Duplicate icons (that is, shortcuts with the same intent, pursuant to some cleanups) are removed. Hotseat icons are migrated in their original place, unless their new location is not accommodated by the hotseat (i.e. the L3 hotseat is too small on this device), in which case they're treated like any other shortcut and tossed into the workspace. To test, turn on Launcher.ENABLE_DEBUG_INTENTS and then: $ adb shell am broadcast -a com.android.launcher3.action.DELETE_DATABASE $ adb shell am broadcast -a com.android.launcher3.action.MIGRATE_DATABASE Bug: 12416411 Change-Id: Ia5c56f36c11455867ea20a39f70210f595020a87 --- src/com/android/launcher3/Launcher.java | 20 +++ src/com/android/launcher3/LauncherModel.java | 32 +++- src/com/android/launcher3/LauncherProvider.java | 224 +++++++++++++++++++++++- 3 files changed, 270 insertions(+), 6 deletions(-) (limited to 'src/com/android') diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index c05769cfa..ceb31b001 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -130,6 +130,8 @@ public class Launcher extends Activity static final boolean DEBUG_RESUME_TIME = false; static final boolean DEBUG_DUMP_LOG = false; + static final boolean ENABLE_DEBUG_INTENTS = false; // allow DebugIntents to run + private static final int REQUEST_CREATE_SHORTCUT = 1; private static final int REQUEST_CREATE_APPWIDGET = 5; private static final int REQUEST_PICK_APPLICATION = 6; @@ -1551,6 +1553,15 @@ public class Launcher extends Activity } else if (Intent.ACTION_USER_PRESENT.equals(action)) { mUserPresent = true; updateRunning(); + } else if (ENABLE_DEBUG_INTENTS && DebugIntents.DELETE_DATABASE.equals(action)) { + mModel.resetLoadedState(false, true); + mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, + LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE); + } else if (ENABLE_DEBUG_INTENTS && DebugIntents.MIGRATE_DATABASE.equals(action)) { + mModel.resetLoadedState(false, true); + mModel.startLoader(false, PagedView.INVALID_RESTORE_PAGE, + LauncherModel.LOADER_FLAG_CLEAR_WORKSPACE + | LauncherModel.LOADER_FLAG_MIGRATE_SHORTCUTS); } } }; @@ -1563,6 +1574,10 @@ public class Launcher extends Activity final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_USER_PRESENT); + if (ENABLE_DEBUG_INTENTS) { + filter.addAction(DebugIntents.DELETE_DATABASE); + filter.addAction(DebugIntents.MIGRATE_DATABASE); + } registerReceiver(mReceiver, filter); FirstFrameAnimatorHelper.initializeDrawListener(getWindow().getDecorView()); mAttached = true; @@ -4667,3 +4682,8 @@ interface LauncherTransitionable { void onLauncherTransitionStep(Launcher l, float t); void onLauncherTransitionEnd(Launcher l, boolean animated, boolean toWorkspace); } + +interface DebugIntents { + static final String DELETE_DATABASE = "com.android.launcher3.action.DELETE_DATABASE"; + static final String MIGRATE_DATABASE = "com.android.launcher3.action.MIGRATE_DATABASE"; +} \ No newline at end of file diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index b2cfb2456..460ab5db2 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -75,6 +75,10 @@ public class LauncherModel extends BroadcastReceiver { // false = strew non-workspace apps across the workspace on upgrade public static final boolean UPGRADE_USE_MORE_APPS_FOLDER = false; + public static final int LOADER_FLAG_NONE = 0; + public static final int LOADER_FLAG_CLEAR_WORKSPACE = 1 << 0; + public static final int LOADER_FLAG_MIGRATE_SHORTCUTS = 1 << 1; + private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons private static final long INVALID_SCREEN_ID = -1L; private final boolean mAppsCanBeOnRemoveableStorage; @@ -1193,6 +1197,10 @@ public class LauncherModel extends BroadcastReceiver { } public void startLoader(boolean isLaunching, int synchronousBindPage) { + startLoader(isLaunching, synchronousBindPage, LOADER_FLAG_NONE); + } + + public void startLoader(boolean isLaunching, int synchronousBindPage, int loadFlags) { synchronized (mLock) { if (DEBUG_LOADERS) { Log.d(TAG, "startLoader isLaunching=" + isLaunching); @@ -1207,7 +1215,7 @@ public class LauncherModel extends BroadcastReceiver { // If there is already one running, tell it to stop. // also, don't downgrade isLaunching if we're already running isLaunching = isLaunching || stopLoaderLocked(); - mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching); + mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching, loadFlags); if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE && mAllAppsLoaded && mWorkspaceLoaded) { mLoaderTask.runBindSynchronousPage(synchronousBindPage); @@ -1298,13 +1306,15 @@ public class LauncherModel extends BroadcastReceiver { private boolean mIsLoadingAndBindingWorkspace; private boolean mStopped; private boolean mLoadAndBindStepFinished; + private int mFlags; private HashMap mLabelCache; - LoaderTask(Context context, boolean isLaunching) { + LoaderTask(Context context, boolean isLaunching, int flags) { mContext = context; mIsLaunching = isLaunching; mLabelCache = new HashMap(); + mFlags = flags; } boolean isLaunching() { @@ -1651,7 +1661,7 @@ public class LauncherModel extends BroadcastReceiver { } } - /** Returns whether this is an upgradge path */ + /** Returns whether this is an upgrade path */ private boolean loadWorkspace() { // Log to disk Launcher.addDumpLog(TAG, "11683562 - loadWorkspace()", true); @@ -1669,8 +1679,20 @@ public class LauncherModel extends BroadcastReceiver { int countX = (int) grid.numColumns; int countY = (int) grid.numRows; - // Make sure the default workspace is loaded, if needed - LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); + if ((mFlags & LOADER_FLAG_CLEAR_WORKSPACE) != 0) { + Launcher.addDumpLog(TAG, "loadWorkspace: resetting launcher database", true); + LauncherAppState.getLauncherProvider().deleteDatabase(); + } + + if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) { + // append the user's Launcher2 shortcuts + Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true); + LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts(); + } else { + // Make sure the default workspace is loaded + Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false); + LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0); + } // Check if we need to do any upgrade-path logic // (Includes having just imported default favorites) diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index 28efd0148..3cacd6c70 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -55,9 +55,11 @@ import com.android.launcher3.config.ProviderConfig; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; public class LauncherProvider extends ContentProvider { @@ -72,7 +74,7 @@ public class LauncherProvider extends ContentProvider { static final String AUTHORITY = ProviderConfig.AUTHORITY; // Should we attempt to load anything from the com.android.launcher2 provider? - static final boolean IMPORT_LAUNCHER2_DATABASE = true; + static final boolean IMPORT_LAUNCHER2_DATABASE = false; static final String TABLE_FAVORITES = "favorites"; static final String TABLE_WORKSPACE_SCREENS = "workspaceScreens"; @@ -133,6 +135,9 @@ public class LauncherProvider extends ContentProvider { private static long dbInsertAndCheck(DatabaseHelper helper, SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) { + if (values == null) { + throw new RuntimeException("Error: attempting to insert null values"); + } if (!values.containsKey(LauncherSettings.Favorites._ID)) { throw new RuntimeException("Error: attempting to add item without specifying an id"); } @@ -287,6 +292,11 @@ public class LauncherProvider extends ContentProvider { } } + public void migrateLauncher2Shortcuts() { + mOpenHelper.migrateLauncher2Shortcuts(mOpenHelper.getWritableDatabase(), + LauncherSettings.Favorites.OLD_CONTENT_URI); + } + private static int getDefaultWorkspaceResourceId() { if (AppsCustomizePagedView.DISABLE_ALL_APPS) { return R.xml.default_workspace_no_all_apps; @@ -306,6 +316,15 @@ public class LauncherProvider extends ContentProvider { return !isTablet && IMPORT_LAUNCHER2_DATABASE; } + public void deleteDatabase() { + // Are you sure? (y/n) + final SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + final String dbFile = db.getPath(); + mOpenHelper.close(); + SQLiteDatabase.deleteDatabase(new File(dbFile)); + mOpenHelper = new DatabaseHelper(getContext()); + } + private static class DatabaseHelper extends SQLiteOpenHelper { private static final String TAG_FAVORITES = "favorites"; private static final String TAG_FAVORITE = "favorite"; @@ -1431,6 +1450,209 @@ public class LauncherProvider extends ContentProvider { } return id; } + + public void migrateLauncher2Shortcuts(SQLiteDatabase db, Uri uri) { + final ContentResolver resolver = mContext.getContentResolver(); + Cursor c = null; + int count = 0; + int curScreen = 0; + + try { + c = resolver.query(uri, null, null, null, "title ASC"); + } catch (Exception e) { + // Ignore + } + + + // We already have a favorites database in the old provider + if (c != null) { + try { + if (c.getCount() > 0) { + final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + final int intentIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT); + final int titleIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + final int iconTypeIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_TYPE); + final int iconIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + final int iconPackageIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); + final int iconResourceIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); + final int containerIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + final int itemTypeIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + final int screenIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); + final int cellXIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); + final int cellYIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + final int uriIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI); + final int displayModeIndex + = c.getColumnIndexOrThrow(LauncherSettings.Favorites.DISPLAY_MODE); + + int i = 0; + int curX = 0; + int curY = 0; + + final LauncherAppState app = LauncherAppState.getInstance(); + final DeviceProfile grid = app.getDynamicGrid().getDeviceProfile(); + final int width = (int) grid.numColumns; + final int height = (int) grid.numRows; + final int hotseatWidth = (int) grid.numHotseatIcons; + + final HashSet seenIntents = new HashSet(c.getCount()); + + final ContentValues[] rows = new ContentValues[c.getCount()]; + + while (c.moveToNext()) { + final int itemType = c.getInt(itemTypeIndex); + if (itemType != Favorites.ITEM_TYPE_APPLICATION + && itemType != Favorites.ITEM_TYPE_SHORTCUT + && itemType != Favorites.ITEM_TYPE_FOLDER) { + continue; + } + + final int cellX = c.getInt(cellXIndex); + final int cellY = c.getInt(cellYIndex); + final int screen = c.getInt(screenIndex); + int container = c.getInt(containerIndex); + final String intentStr = c.getString(intentIndex); + Launcher.addDumpLog(TAG, "migrating \"" + + c.getString(titleIndex) + "\": " + intentStr, true); + + if (itemType != Favorites.ITEM_TYPE_FOLDER) { + if (TextUtils.isEmpty(intentStr)) { + // no intent? no icon + Launcher.addDumpLog(TAG, "skipping empty intent", true); + continue; + } else { + try { + // Canonicalize + final Intent intent = Intent.parseUri(intentStr, 0); + // the Play Store sets the package parameter, but Launcher + // does not, so we clear that out to keep them the same + intent.setPackage(null); + final String key = intent.toUri(0); + if (seenIntents.contains(key)) { + Launcher.addDumpLog(TAG, "skipping duplicate", true); + continue; + } else { + seenIntents.add(key); + } + } catch (URISyntaxException e) { + // bogus intent? + Launcher.addDumpLog(TAG, + "skipping invalid intent uri", true); + continue; + } + } + } + + ContentValues values = new ContentValues(c.getColumnCount()); + values.put(LauncherSettings.Favorites._ID, c.getInt(idIndex)); + values.put(LauncherSettings.Favorites.INTENT, intentStr); + values.put(LauncherSettings.Favorites.TITLE, c.getString(titleIndex)); + values.put(LauncherSettings.Favorites.ICON_TYPE, + c.getInt(iconTypeIndex)); + values.put(LauncherSettings.Favorites.ICON, c.getBlob(iconIndex)); + values.put(LauncherSettings.Favorites.ICON_PACKAGE, + c.getString(iconPackageIndex)); + values.put(LauncherSettings.Favorites.ICON_RESOURCE, + c.getString(iconResourceIndex)); + values.put(LauncherSettings.Favorites.ITEM_TYPE, itemType); + values.put(LauncherSettings.Favorites.APPWIDGET_ID, -1); + values.put(LauncherSettings.Favorites.URI, c.getString(uriIndex)); + values.put(LauncherSettings.Favorites.DISPLAY_MODE, + c.getInt(displayModeIndex)); + + if (container == LauncherSettings.Favorites.CONTAINER_HOTSEAT + && screen >= hotseatWidth) { + // no room for you in the hotseat? it's off to the desktop with you + container = Favorites.CONTAINER_DESKTOP; + } + + if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP) { + // In a folder or in the hotseat, preserve position + values.put(LauncherSettings.Favorites.SCREEN, screen); + values.put(LauncherSettings.Favorites.CELLX, cellX); + values.put(LauncherSettings.Favorites.CELLY, cellY); + } else { + values.put(LauncherSettings.Favorites.SCREEN, curScreen); + values.put(LauncherSettings.Favorites.CELLX, curX); + values.put(LauncherSettings.Favorites.CELLY, curY); + curX = (curX + 1) % width; + if (curX == 0) { + curY = (curY + 1); + } + // Leave the last row of icons blank on screen 0 + if (curScreen == 0 && curY == height - 1 || curY == height) { + curScreen = (int) generateNewScreenId(); + curY = 0; + } + } + + values.put(LauncherSettings.Favorites.CONTAINER, container); + + rows[i++] = values; + } + + if (i > 0) { + db.beginTransaction(); + try { + final int N = rows.length; + for (i = 0; i < N; i++) { + if (rows[i] == null) continue; + if (dbInsertAndCheck(this, db, TABLE_FAVORITES, null, rows[i]) + < 0) { + return; + } else { + count++; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + + db.beginTransaction(); + try { + for (i=0; i<=curScreen; i++) { + final ContentValues values = new ContentValues(); + values.put(LauncherSettings.WorkspaceScreens._ID, i); + values.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); + if (dbInsertAndCheck(this, db, TABLE_WORKSPACE_SCREENS, null, values) + < 0) { + return; + } + } + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + } finally { + c.close(); + } + } + + Launcher.addDumpLog(TAG, "migrated " + count + " icons from Launcher2 into " + + (curScreen+1) + " screens", true); + + // ensure that new screens are created to hold these icons + setFlagJustLoadedOldDb(); + + // Update max IDs; very important since we just grabbed IDs from another database + mMaxItemId = initializeMaxItemId(db); + mMaxScreenId = initializeMaxScreenId(db); + if (LOGD) Log.d(TAG, "mMaxItemId: " + mMaxItemId + " mMaxScreenId: " + mMaxScreenId); + } } /** -- cgit v1.2.3