diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2016-07-08 08:32:44 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2016-07-09 16:19:26 -0700 |
commit | a5c8a9eb666da16bc4c9ea4412868e22ace8d1f0 (patch) | |
tree | 1e612ea9a361beae540c943d18af1ad233fb0f5d /src/com | |
parent | f03bd4f5470eed9808a0e6f345de94f4e578ae85 (diff) | |
download | android_packages_apps_Trebuchet-a5c8a9eb666da16bc4c9ea4412868e22ace8d1f0.tar.gz android_packages_apps_Trebuchet-a5c8a9eb666da16bc4c9ea4412868e22ace8d1f0.tar.bz2 android_packages_apps_Trebuchet-a5c8a9eb666da16bc4c9ea4412868e22ace8d1f0.zip |
Adding logic to pull in workspace data from another Launcher3 based
provider. This allows OEMs to keep the user's homescreen intact while
changing the default home app package.
Bug: 28536314
Change-Id: Ibebfd7dd33aa2cbd9ca28d2d611dd0a4a5971444
Diffstat (limited to 'src/com')
9 files changed, 543 insertions, 65 deletions
diff --git a/src/com/android/launcher3/DefaultLayoutParser.java b/src/com/android/launcher3/DefaultLayoutParser.java index 2bba38006..e6f34d952 100644 --- a/src/com/android/launcher3/DefaultLayoutParser.java +++ b/src/com/android/launcher3/DefaultLayoutParser.java @@ -33,7 +33,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { private static final String TAG_FAVORITES = "favorites"; protected static final String TAG_FAVORITE = "favorite"; private static final String TAG_APPWIDGET = "appwidget"; - private static final String TAG_SHORTCUT = "shortcut"; + protected static final String TAG_SHORTCUT = "shortcut"; private static final String TAG_FOLDER = "folder"; private static final String TAG_PARTNER_FOLDER = "partner-folder"; @@ -89,7 +89,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * AppShortcutParser which also supports adding URI based intents */ - @Thunk class AppShortcutWithUriParser extends AppShortcutParser { + public class AppShortcutWithUriParser extends AppShortcutParser { @Override protected long invalidPackageOrClass(XmlResourceParser parser) { @@ -179,7 +179,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * Shortcut parser which allows any uri and not just web urls. */ - private class UriShortcutParser extends ShortcutParser { + public class UriShortcutParser extends ShortcutParser { public UriShortcutParser(Resources iconRes) { super(iconRes); @@ -201,7 +201,7 @@ public class DefaultLayoutParser extends AutoInstallsLayout { /** * Contains a list of <favorite> nodes, and accepts the first successfully parsed node. */ - protected class ResolveParser implements TagParser { + public class ResolveParser implements TagParser { private final AppShortcutWithUriParser mChildParser = new AppShortcutWithUriParser(); diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index d371a869c..7c8bfabf7 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -62,8 +62,8 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc mLongPressHelper = new CheckLongPressHelper(this); mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mDragLayer = ((Launcher) context).getDragLayer(); - setAccessibilityDelegate(((Launcher) context).getAccessibilityDelegate()); + mDragLayer = Launcher.getLauncher(context).getDragLayer(); + setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); } diff --git a/src/com/android/launcher3/LauncherFiles.java b/src/com/android/launcher3/LauncherFiles.java index adb503135..9c4646b8e 100644 --- a/src/com/android/launcher3/LauncherFiles.java +++ b/src/com/android/launcher3/LauncherFiles.java @@ -14,33 +14,20 @@ public class LauncherFiles { private static final String XML = ".xml"; - public static final String DEFAULT_WALLPAPER_THUMBNAIL = "default_thumb2.jpg"; - public static final String DEFAULT_WALLPAPER_THUMBNAIL_OLD = "default_thumb.jpg"; public static final String LAUNCHER_DB = "launcher.db"; public static final String SHARED_PREFERENCES_KEY = "com.android.launcher3.prefs"; - public static final String WALLPAPER_CROP_PREFERENCES_KEY = - "com.android.launcher3.WallpaperCropActivity"; public static final String MANAGED_USER_PREFERENCES_KEY = "com.android.launcher3.managedusers.prefs"; + // This preference file is not backed up to cloud. + public static final String DEVICE_PREFERENCES_KEY = "com.android.launcher3.device.prefs"; - public static final String WALLPAPER_IMAGES_DB = "saved_wallpaper_images.db"; public static final String WIDGET_PREVIEWS_DB = "widgetpreviews.db"; public static final String APP_ICONS_DB = "app_icons.db"; public static final List<String> ALL_FILES = Collections.unmodifiableList(Arrays.asList( - DEFAULT_WALLPAPER_THUMBNAIL, - DEFAULT_WALLPAPER_THUMBNAIL_OLD, LAUNCHER_DB, SHARED_PREFERENCES_KEY + XML, - WALLPAPER_CROP_PREFERENCES_KEY + XML, - WALLPAPER_IMAGES_DB, WIDGET_PREVIEWS_DB, MANAGED_USER_PREFERENCES_KEY + XML, + DEVICE_PREFERENCES_KEY + XML, APP_ICONS_DB)); - - // TODO: Delete these files on upgrade - public static final List<String> OBSOLETE_FILES = Collections.unmodifiableList(Arrays.asList( - "launches.logTap", - "stats.logTap", - "launcher.preferences", - "com.android.launcher3.compat.PackageInstallerCompatV16.queue")); } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 3e98c1336..39e28c0b0 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -60,6 +60,7 @@ import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.WidgetsModel; +import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; @@ -1648,7 +1649,14 @@ public class LauncherModel extends BroadcastReceiver int countY = profile.numRows; boolean clearDb = false; - if (GridSizeMigrationTask.ENABLED && + try { + ImportDataTask.performImportIfPossible(context); + } catch (Exception e) { + // Migration failed. Clear workspace. + clearDb = true; + } + + if (!clearDb && GridSizeMigrationTask.ENABLED && !GridSizeMigrationTask.migrateGridIfNeeded(mContext)) { // Migration failed. Clear workspace. clearDb = true; diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java index a79df0daf..eee562781 100644 --- a/src/com/android/launcher3/LauncherProvider.java +++ b/src/com/android/launcher3/LauncherProvider.java @@ -359,6 +359,12 @@ public class LauncherProvider extends ContentProvider { clearFlagEmptyDbCreated(); return null; } + case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : { + Bundle result = new Bundle(); + result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE, + Utilities.getPrefs(getContext()).getBoolean(EMPTY_DATABASE_CREATED, false)); + return result; + } case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: { Bundle result = new Bundle(); result.putSerializable(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders()); diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java index 8157eb328..e88439306 100644 --- a/src/com/android/launcher3/LauncherSettings.java +++ b/src/com/android/launcher3/LauncherSettings.java @@ -283,6 +283,7 @@ public class LauncherSettings { ProviderConfig.AUTHORITY + "/settings"); public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag"; + public static final String METHOD_WAS_EMPTY_DB_CREATED = "get_empty_db_flag"; public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders"; diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index a455026fa..f01c7f259 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -32,6 +32,7 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.view.View; import android.view.View.OnClickListener; @@ -63,9 +64,9 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen @TargetApi(Build.VERSION_CODES.LOLLIPOP) public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info, boolean disabledForSafeMode) { - super(context); + super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme)); - mLauncher = (Launcher) context; + mLauncher = Launcher.getLauncher(context); mInfo = info; mStartState = info.restoreStatus; mIconLookupIntent = new Intent().setComponent(info.providerName); diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java index 7287ced7d..600768e5d 100644 --- a/src/com/android/launcher3/model/GridSizeMigrationTask.java +++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java @@ -281,7 +281,9 @@ public class GridSizeMigrationTask { // Try removing all possible combinations for (int x = 0; x < mSrcX; x++) { - for (int y = startY; y < mSrcY; y++) { + // Try removing the rows first from bottom. This keeps the workspace + // nicely aligned with hotseat. + for (int y = mSrcY - 1; y >= startY; y--) { // Use a deep copy when trying out a particular combination as it can change // the underlying object. ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items), outLoss); @@ -879,6 +881,14 @@ public class GridSizeMigrationTask { return String.format(Locale.ENGLISH, "%d,%d", x, y); } + public static void markForMigration( + Context context, int gridX, int gridY, int hotseatSize) { + Utilities.getPrefs(context).edit() + .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(gridX, gridY)) + .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, hotseatSize) + .apply(); + } + /** * Migrates the workspace and hotseat in case their sizes changed. * @return false if the migration failed. @@ -915,45 +925,8 @@ public class GridSizeMigrationTask { Point sourceSize = parsePoint(prefs.getString( KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)); - if (!targetSize.equals(sourceSize)) { - - // The following list defines all possible grid sizes (and intermediate steps - // during migration). Note that at each step, dx <= 1 && dy <= 1. Any grid size - // which is not in this list is not migrated. - // Note that the InvariantDeviceProfile defines (rows, cols) but the Points - // specified here are defined as (cols, rows). - ArrayList<Point> gridSizeSteps = new ArrayList<>(); - gridSizeSteps.add(new Point(3, 2)); - gridSizeSteps.add(new Point(3, 3)); - gridSizeSteps.add(new Point(4, 3)); - gridSizeSteps.add(new Point(4, 4)); - gridSizeSteps.add(new Point(5, 5)); - gridSizeSteps.add(new Point(6, 5)); - gridSizeSteps.add(new Point(6, 6)); - gridSizeSteps.add(new Point(7, 7)); - - int sourceSizeIndex = gridSizeSteps.indexOf(sourceSize); - int targetSizeIndex = gridSizeSteps.indexOf(targetSize); - - if (sourceSizeIndex <= -1 || targetSizeIndex <= -1) { - throw new Exception("Unable to migrate grid size from " + sourceSize - + " to " + targetSize); - } - - // Migrate the workspace grid, step by step. - while (targetSizeIndex < sourceSizeIndex ) { - // We only need to migrate the grid if source size is greater - // than the target size. - Point stepTargetSize = gridSizeSteps.get(sourceSizeIndex - 1); - Point stepSourceSize = gridSizeSteps.get(sourceSizeIndex); - - if (new GridSizeMigrationTask(context, - LauncherAppState.getInstance().getInvariantDeviceProfile(), - validPackages, stepSourceSize, stepTargetSize).migrateWorkspace()) { - dbChanged = true; - } - sourceSizeIndex--; - } + if (new MultiStepMigrationTask(validPackages, context).migrate(sourceSize, targetSize)) { + dbChanged = true; } if (dbChanged) { @@ -999,4 +972,55 @@ public class GridSizeMigrationTask { .updateAndGetActiveSessionCache().keySet()); return validPackages; } + + /** + * Task to run grid migration in multiple steps when the size difference is more than 1. + */ + protected static class MultiStepMigrationTask { + private final HashSet<String> mValidPackages; + private final Context mContext; + + public MultiStepMigrationTask(HashSet<String> validPackages, Context context) { + mValidPackages = validPackages; + mContext = context; + } + + public boolean migrate(Point sourceSize, Point targetSize) throws Exception { + boolean dbChanged = false; + if (!targetSize.equals(sourceSize)) { + if (sourceSize.x < targetSize.x) { + // Source is smaller that target, just expand the grid without actual migration. + sourceSize.x = targetSize.x; + } + if (sourceSize.y < targetSize.y) { + // Source is smaller that target, just expand the grid without actual migration. + sourceSize.y = targetSize.y; + } + + // Migrate the workspace grid, such that the points differ by max 1 in x and y + // each on every step. + while (!targetSize.equals(sourceSize)) { + // Get the next size, such that the points differ by max 1 in x and y each + Point nextSize = new Point(sourceSize); + if (targetSize.x < nextSize.x) { + nextSize.x--; + } + if (targetSize.y < nextSize.y) { + nextSize.y--; + } + if (runStepTask(sourceSize, nextSize)) { + dbChanged = true; + } + sourceSize.set(nextSize.x, nextSize.y); + } + } + return dbChanged; + } + + protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception { + return new GridSizeMigrationTask(mContext, + LauncherAppState.getInstance().getInvariantDeviceProfile(), + mValidPackages, sourceSize, nextSize).migrateWorkspace(); + } + } } diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java new file mode 100644 index 000000000..233c3edf1 --- /dev/null +++ b/src/com/android/launcher3/provider/ImportDataTask.java @@ -0,0 +1,451 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.provider; + +import android.content.ContentProviderOperation; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.os.Process; +import android.text.TextUtils; +import android.util.LongSparseArray; +import android.util.SparseBooleanArray; + +import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback; +import com.android.launcher3.DefaultLayoutParser; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherAppWidgetInfo; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.LauncherSettings.Settings; +import com.android.launcher3.LauncherSettings.WorkspaceScreens; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; +import com.android.launcher3.compat.UserHandleCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.config.ProviderConfig; +import com.android.launcher3.logging.FileLog; +import com.android.launcher3.model.GridSizeMigrationTask; +import com.android.launcher3.util.LongArrayMap; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Utility class to import data from another Launcher which is based on Launcher3 schema. + */ +public class ImportDataTask { + + public static final String KEY_DATA_IMPORT_SRC_PKG = "data_import_src_pkg"; + public static final String KEY_DATA_IMPORT_SRC_AUTHORITY = "data_import_src_authority"; + + private static final String TAG = "ImportDataTask"; + private static final int MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION = 6; + // Insert items progressively to avoid OOM exception when loading icons. + private static final int BATCH_INSERT_SIZE = 15; + + private final Context mContext; + + private final Uri mOtherScreensUri; + private final Uri mOtherFavoritesUri; + + private int mHotseatSize; + private int mMaxGridSizeX; + private int mMaxGridSizeY; + + private ImportDataTask(Context context, String sourceAuthority) { + mContext = context; + mOtherScreensUri = Uri.parse("content://" + + sourceAuthority + "/" + WorkspaceScreens.TABLE_NAME); + mOtherFavoritesUri = Uri.parse("content://" + sourceAuthority + "/" + Favorites.TABLE_NAME); + } + + public boolean importWorkspace() throws Exception { + ArrayList<Long> allScreens = LauncherDbUtils.getScreenIdsFromCursor( + mContext.getContentResolver().query(mOtherScreensUri, null, null, null, + LauncherSettings.WorkspaceScreens.SCREEN_RANK)); + + // During import we reset the screen IDs to 0-indexed values. + if (allScreens.isEmpty()) { + // No thing to migrate + return false; + } + + mHotseatSize = mMaxGridSizeX = mMaxGridSizeY = 0; + + // Build screen update + ArrayList<ContentProviderOperation> screenOps = new ArrayList<>(); + int count = allScreens.size(); + LongSparseArray<Long> screenIdMap = new LongSparseArray<>(count); + for (int i = 0; i < count; i++) { + ContentValues v = new ContentValues(); + v.put(LauncherSettings.WorkspaceScreens._ID, i); + v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i); + screenIdMap.put(allScreens.get(i), (long) i); + screenOps.add(ContentProviderOperation.newInsert( + LauncherSettings.WorkspaceScreens.CONTENT_URI).withValues(v).build()); + } + mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, screenOps); + importWorkspaceItems(allScreens.get(0), screenIdMap); + + GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize); + + // Create empty DB flag. + LauncherSettings.Settings.call(mContext.getContentResolver(), + LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG); + return true; + } + + /** + * 1) Imports all the workspace entries from the source provider. + * 2) For home screen entries, maps the screen id based on {@param screenIdMap} + * 3) In the end fills any holes in hotseat with items from default hotseat layout. + */ + private void importWorkspaceItems( + long firsetScreenId, LongSparseArray<Long> screenIdMap) throws Exception { + String profileId = Long.toString(UserManagerCompat.getInstance(mContext) + .getSerialNumberForUser(UserHandleCompat.myUserHandle())); + + boolean createEmptyRowOnFirstScreen = false; + if (FeatureFlags.QSB_ON_FIRST_SCREEN) { + try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null, + // get items on the first row of the first screen + "profileId = ? AND container = -100 AND screen = ? AND cellY = 0", + new String[]{profileId, Long.toString(firsetScreenId)}, + null)) { + // First row of first screen is not empty + createEmptyRowOnFirstScreen = c.moveToNext(); + } + } + + ArrayList<ContentProviderOperation> insertOperations = new ArrayList<>(BATCH_INSERT_SIZE); + + // Set of package names present in hotseat + final HashSet<String> hotseatTargetApps = new HashSet<>(); + final LongArrayMap<Intent> hotseatItems = new LongArrayMap<>(); + int maxId = 0; + + // Number of imported items on workspace and hotseat + int totalItemsOnWorkspace = 0; + + try (Cursor c = mContext.getContentResolver() + .query(mOtherFavoritesUri, null, + // Only migrate the primary user + Favorites.PROFILE_ID + " = ?", new String[]{profileId}, + // Get the items sorted by container, so that the folders are loaded + // before the corresponding items. + Favorites.CONTAINER)) { + + // various columns we expect to exist. + final int idIndex = c.getColumnIndexOrThrow(Favorites._ID); + final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT); + final int titleIndex = c.getColumnIndexOrThrow(Favorites.TITLE); + final int containerIndex = c.getColumnIndexOrThrow(Favorites.CONTAINER); + final int itemTypeIndex = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE); + final int widgetProviderIndex = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER); + final int screenIndex = c.getColumnIndexOrThrow(Favorites.SCREEN); + final int cellXIndex = c.getColumnIndexOrThrow(Favorites.CELLX); + final int cellYIndex = c.getColumnIndexOrThrow(Favorites.CELLY); + final int spanXIndex = c.getColumnIndexOrThrow(Favorites.SPANX); + final int spanYIndex = c.getColumnIndexOrThrow(Favorites.SPANY); + final int rankIndex = c.getColumnIndexOrThrow(Favorites.RANK); + final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON); + final int iconPackageIndex = c.getColumnIndexOrThrow(Favorites.ICON_PACKAGE); + final int iconResourceIndex = c.getColumnIndexOrThrow(Favorites.ICON_RESOURCE); + + SparseBooleanArray mValidFolders = new SparseBooleanArray(); + ContentValues values = new ContentValues(); + + while (c.moveToNext()) { + values.clear(); + int id = c.getInt(idIndex); + maxId = Math.max(maxId, id); + int type = c.getInt(itemTypeIndex); + int container = c.getInt(containerIndex); + + long screen = c.getLong(screenIndex); + + int cellX = c.getInt(cellXIndex); + int cellY = c.getInt(cellYIndex); + int spanX = c.getInt(spanXIndex); + int spanY = c.getInt(spanYIndex); + + switch (container) { + case Favorites.CONTAINER_DESKTOP: { + Long newScreenId = screenIdMap.get(screen); + if (newScreenId == null) { + FileLog.d(TAG, String.format("Skipping item %d, type %d not on a valid screen %d", id, type, screen)); + continue; + } + // Reset the screen to 0-index value + screen = newScreenId; + if (createEmptyRowOnFirstScreen && screen == Workspace.FIRST_SCREEN_ID) { + // Shift items by 1. + cellY++; + } + + mMaxGridSizeX = Math.max(mMaxGridSizeX, cellX + spanX); + mMaxGridSizeY = Math.max(mMaxGridSizeY, cellY + spanY); + break; + } + case Favorites.CONTAINER_HOTSEAT: { + mHotseatSize = Math.max(mHotseatSize, (int) screen + 1); + break; + } + default: + if (!mValidFolders.get(container)) { + FileLog.d(TAG, String.format("Skipping item %d, type %d not in a valid folder %d", id, type, container)); + continue; + } + } + + Intent intent = null; + switch (type) { + case Favorites.ITEM_TYPE_FOLDER: { + mValidFolders.put(id, true); + // Use a empty intent to indicate a folder. + intent = new Intent(); + break; + } + case Favorites.ITEM_TYPE_APPWIDGET: { + values.put(Favorites.RESTORED, + LauncherAppWidgetInfo.FLAG_ID_NOT_VALID | + LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY | + LauncherAppWidgetInfo.FLAG_UI_NOT_READY); + values.put(Favorites.APPWIDGET_PROVIDER, c.getString(widgetProviderIndex)); + break; + } + case Favorites.ITEM_TYPE_SHORTCUT: + case Favorites.ITEM_TYPE_APPLICATION: { + intent = Intent.parseUri(c.getString(intentIndex), 0); + if (Utilities.isLauncherAppTarget(intent)) { + type = Favorites.ITEM_TYPE_APPLICATION; + } else { + values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex)); + values.put(Favorites.ICON_RESOURCE, c.getString(iconResourceIndex)); + } + values.put(Favorites.ICON, c.getBlob(iconIndex)); + values.put(Favorites.INTENT, intent.toUri(0)); + values.put(Favorites.RANK, c.getInt(rankIndex)); + + values.put(Favorites.RESTORED, 1); + break; + } + default: + FileLog.d(TAG, String.format("Skipping item %d, not a valid type %d", id, type)); + continue; + } + + if (container == Favorites.CONTAINER_HOTSEAT) { + if (intent == null) { + FileLog.d(TAG, String.format("Skipping item %d, null intent on hotseat", id)); + continue; + } + if (intent.getComponent() != null) { + intent.setPackage(intent.getComponent().getPackageName()); + } + hotseatItems.put(screen, intent); + hotseatTargetApps.add(getPackage(intent)); + } + + values.put(Favorites._ID, id); + values.put(Favorites.ITEM_TYPE, type); + values.put(Favorites.CONTAINER, container); + values.put(Favorites.SCREEN, screen); + values.put(Favorites.CELLX, cellX); + values.put(Favorites.CELLY, cellY); + values.put(Favorites.SPANX, spanX); + values.put(Favorites.SPANY, spanY); + values.put(Favorites.TITLE, c.getString(titleIndex)); + insertOperations.add(ContentProviderOperation + .newInsert(Favorites.CONTENT_URI).withValues(values).build()); + if (container < 0) { + totalItemsOnWorkspace++; + } + + if (insertOperations.size() >= BATCH_INSERT_SIZE) { + mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + insertOperations); + insertOperations.clear(); + } + } + } + if (totalItemsOnWorkspace < MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION) { + throw new Exception("Insufficient data"); + } + + int myHotseatCount = LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons; + if (!FeatureFlags.NO_ALL_APPS_ICON) { + myHotseatCount--; + } + if (hotseatItems.size() < myHotseatCount) { + // Insufficient hotseat items. Add a few more. + HotseatParserCallback parserCallback = new HotseatParserCallback( + hotseatTargetApps, hotseatItems, insertOperations, maxId + 1); + new HotseatLayoutParser(mContext, + parserCallback).loadLayout(null, new ArrayList<Long>()); + mHotseatSize = (int) hotseatItems.keyAt(hotseatItems.size() - 1) + 1; + } + if (!insertOperations.isEmpty()) { + mContext.getContentResolver().applyBatch(ProviderConfig.AUTHORITY, + insertOperations); + } + } + + private static final String getPackage(Intent intent) { + return intent.getComponent() != null ? intent.getComponent().getPackageName() + : intent.getPackage(); + } + + /** + * Performs data import if possible. + * @return true on successful data import, false if it was not available + * @throws Exception if the import failed + */ + public static boolean performImportIfPossible(Context context) throws Exception { + SharedPreferences devicePrefs = getDevicePrefs(context); + String sourcePackage = devicePrefs.getString(KEY_DATA_IMPORT_SRC_PKG, ""); + String sourceAuthority = devicePrefs.getString(KEY_DATA_IMPORT_SRC_AUTHORITY, ""); + + if (TextUtils.isEmpty(sourcePackage) || TextUtils.isEmpty(sourceAuthority)) { + return false; + } + + // Synchronously clear the migration flags. This ensures that we do not try migration + // again and thus prevents potential crash loops due to migration failure. + devicePrefs.edit().remove(KEY_DATA_IMPORT_SRC_PKG).remove(KEY_DATA_IMPORT_SRC_AUTHORITY).commit(); + + if (!Settings.call(context.getContentResolver(), Settings.METHOD_WAS_EMPTY_DB_CREATED) + .getBoolean(Settings.EXTRA_VALUE, false)) { + // Only migration if a new DB was created. + return false; + } + + for (ProviderInfo info : context.getPackageManager().queryContentProviders( + null, context.getApplicationInfo().uid, 0)) { + + if (sourcePackage.equals(info.packageName)) { + if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { + // Only migrate if the source launcher is also on system image. + return false; + } + + // Wait until we found a provider with matching authority. + if (sourceAuthority.equals(info.authority)) { + if (TextUtils.isEmpty(info.readPermission) || + context.checkPermission(info.readPermission, Process.myPid(), + Process.myUid()) == PackageManager.PERMISSION_GRANTED) { + // All checks passed, run the import task. + return new ImportDataTask(context, sourceAuthority).importWorkspace(); + } + } + } + } + return false; + } + + private static SharedPreferences getDevicePrefs(Context c) { + return c.getSharedPreferences(LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE); + } + + private static final int getMyHotseatLayoutId() { + return LauncherAppState.getInstance().getInvariantDeviceProfile().numHotseatIcons <= 5 + ? R.xml.dw_phone_hotseat + : R.xml.dw_tablet_hotseat; + } + + /** + * Extension of {@link DefaultLayoutParser} which only allows icons and shortcuts. + */ + private static class HotseatLayoutParser extends DefaultLayoutParser { + public HotseatLayoutParser(Context context, LayoutParserCallback callback) { + super(context, null, callback, context.getResources(), getMyHotseatLayoutId()); + } + + @Override + protected HashMap<String, TagParser> getLayoutElementsMap() { + // Only allow shortcut parsers + HashMap<String, TagParser> parsers = new HashMap<String, TagParser>(); + parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser()); + parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes)); + parsers.put(TAG_RESOLVE, new ResolveParser()); + return parsers; + } + } + + /** + * {@link LayoutParserCallback} which adds items in empty hotseat spots. + */ + private static class HotseatParserCallback implements LayoutParserCallback { + private final HashSet<String> mExisitingApps; + private final LongArrayMap<Intent> mExistingItems; + private final ArrayList<ContentProviderOperation> mOutOps; + private int mStartItemId; + + HotseatParserCallback( + HashSet<String> existingApps, LongArrayMap<Intent> existingItems, + ArrayList<ContentProviderOperation> outOps, int startItemId) { + mExisitingApps = existingApps; + mExistingItems = existingItems; + mOutOps = outOps; + mStartItemId = startItemId; + } + + @Override + public long generateNewItemId() { + return mStartItemId++; + } + + @Override + public long insertAndCheck(SQLiteDatabase db, ContentValues values) { + Intent intent; + try { + intent = Intent.parseUri(values.getAsString(Favorites.INTENT), 0); + } catch (URISyntaxException e) { + return 0; + } + String pkg = getPackage(intent); + if (pkg == null || mExisitingApps.contains(pkg)) { + // The item does not target an app or is already in hotseat. + return 0; + } + mExisitingApps.add(pkg); + + // find next vacant spot. + long screen = 0; + while (mExistingItems.get(screen) != null) { + screen++; + } + mExistingItems.put(screen, intent); + values.put(Favorites.SCREEN, screen); + mOutOps.add(ContentProviderOperation.newInsert(Favorites.CONTENT_URI).withValues(values).build()); + return 0; + } + } +} |