mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-02-27 15:26:58 +00:00
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
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
451
src/com/android/launcher3/provider/ImportDataTask.java
Normal file
451
src/com/android/launcher3/provider/ImportDataTask.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Point;
|
||||
@@ -12,10 +13,12 @@ import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.config.ProviderConfig;
|
||||
import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
|
||||
import com.android.launcher3.util.TestLauncherProvider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GridSizeMigrationTask}
|
||||
@@ -337,7 +340,7 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro
|
||||
} else {
|
||||
assertEquals(1, c.getCount());
|
||||
c.moveToNext();
|
||||
assertEquals(String.format("Failed to verify item ad %d %d, %d", i, y, x),
|
||||
assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
|
||||
id, c.getLong(0));
|
||||
total++;
|
||||
}
|
||||
@@ -388,4 +391,53 @@ public class GridSizeMigrationTaskTest extends ProviderTestCase2<TestLauncherPro
|
||||
getMockContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
|
||||
return id;
|
||||
}
|
||||
|
||||
public void testMultiStepMigration_small_to_large() throws Exception {
|
||||
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
|
||||
verifier.migrate(new Point(3, 3), new Point(5, 5));
|
||||
verifier.assertCompleted();
|
||||
}
|
||||
|
||||
public void testMultiStepMigration_large_to_small() throws Exception {
|
||||
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
|
||||
5, 5, 4, 4,
|
||||
4, 4, 3, 4
|
||||
);
|
||||
verifier.migrate(new Point(5, 5), new Point(3, 4));
|
||||
verifier.assertCompleted();
|
||||
}
|
||||
|
||||
public void testMultiStepMigration_zig_zag() throws Exception {
|
||||
MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
|
||||
5, 7, 4, 7,
|
||||
4, 7, 3, 7
|
||||
);
|
||||
verifier.migrate(new Point(5, 5), new Point(3, 7));
|
||||
verifier.assertCompleted();
|
||||
}
|
||||
|
||||
private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
|
||||
|
||||
private final LinkedList<Point> mPoints;
|
||||
|
||||
public MultiStepMigrationTaskVerifier(int... points) {
|
||||
super(null, null);
|
||||
|
||||
mPoints = new LinkedList<>();
|
||||
for (int i = 0; i < points.length; i += 2) {
|
||||
mPoints.add(new Point(points[i], points[i + 1]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
|
||||
assertEquals(sourceSize, mPoints.poll());
|
||||
assertEquals(nextSize, mPoints.poll());
|
||||
return false;
|
||||
}
|
||||
|
||||
public void assertCompleted() {
|
||||
assertTrue(mPoints.isEmpty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user