summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-07-08 08:32:44 -0700
committerSunny Goyal <sunnygoyal@google.com>2016-07-09 16:19:26 -0700
commita5c8a9eb666da16bc4c9ea4412868e22ace8d1f0 (patch)
tree1e612ea9a361beae540c943d18af1ad233fb0f5d /src/com/android
parentf03bd4f5470eed9808a0e6f345de94f4e578ae85 (diff)
downloadandroid_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/android')
-rw-r--r--src/com/android/launcher3/DefaultLayoutParser.java8
-rw-r--r--src/com/android/launcher3/LauncherAppWidgetHostView.java4
-rw-r--r--src/com/android/launcher3/LauncherFiles.java19
-rw-r--r--src/com/android/launcher3/LauncherModel.java10
-rw-r--r--src/com/android/launcher3/LauncherProvider.java6
-rw-r--r--src/com/android/launcher3/LauncherSettings.java1
-rw-r--r--src/com/android/launcher3/PendingAppWidgetHostView.java5
-rw-r--r--src/com/android/launcher3/model/GridSizeMigrationTask.java104
-rw-r--r--src/com/android/launcher3/provider/ImportDataTask.java451
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;
+ }
+ }
+}