summaryrefslogtreecommitdiffstats
path: root/src/com/android
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2016-07-13 16:02:18 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2016-07-13 16:02:18 +0000
commitba162aa7d2a49c09c00ff3184ffda3bea0e55509 (patch)
treec710837d19924e755e5df101a5ca60b32d1e455b /src/com/android
parentc6e5fdb7acf01d4c5824f9e3e212d046c90d61c1 (diff)
parenta5c8a9eb666da16bc4c9ea4412868e22ace8d1f0 (diff)
downloadandroid_packages_apps_Trebuchet-ba162aa7d2a49c09c00ff3184ffda3bea0e55509.tar.gz
android_packages_apps_Trebuchet-ba162aa7d2a49c09c00ff3184ffda3bea0e55509.tar.bz2
android_packages_apps_Trebuchet-ba162aa7d2a49c09c00ff3184ffda3bea0e55509.zip
Merge "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." into ub-launcher3-calgary
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;
+ }
+ }
+}