Render user's actual workspace in ThemePicker preview (Part 3)

go/grid-migration-preview

With this change, we can see actual grid migration in wallpaper preview.

The approach here: we use a tmp table (favorites_preview) here specifically for this preview (to write off the migration results), and load from this tmp table workspace items if migration is necessary and successful. Otherwise, we load from the current workspace.

UPDATED: this change should be completely compatible with the new multi-db grid migration algorithm. Here is why
1. In LauncherPreviewRender#renderScreenShot, I added a check to decide which grid migration preview method we should call. Once v2 preview method is implemented, it should be integrated with other parts of this change perfectly (the reason will be mentioned below).
2. While we have multiple DBs, mOpenHelper in LauncherProvider always points to the current db we are using. Queries using CONTENT_URI is routed to whatever DB mOpenHelper points to, so it works perfectly to directly operate on CONTENT_URI even when we use multi-db underneath the hood.
3. With 1 and 2 mentioned, I believe in order for this preview change to support multi-db, we only need to implement the V2 grid migration algorithm. Because most of what we are doing in this change is wrapped in GridSizeMigrationTask, it's perfectly safeguarded.

Bug: 144052839
Change-Id: Ie6d6048d77326f96546c8a180a7cd8f15b47e4c4
This commit is contained in:
Tracy Zhou 2020-01-12 01:07:59 -08:00
parent 1562d104b8
commit be13d109b7
16 changed files with 419 additions and 142 deletions

View File

@ -65,7 +65,7 @@ public class GridSizeMigrationTaskTest {
};
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@ -82,7 +82,7 @@ public class GridSizeMigrationTaskTest {
};
mIdp.numHotseatIcons = 3;
new GridSizeMigrationTask(mContext, mDb, mValidPackages, 5, 3)
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
.migrateHotseat();
// First item is dropped as it has the least weight.
verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
@ -127,7 +127,7 @@ public class GridSizeMigrationTaskTest {
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Column 2 and row 2 got removed.
@ -147,7 +147,7 @@ public class GridSizeMigrationTaskTest {
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column get moved to new screen
@ -172,7 +172,7 @@ public class GridSizeMigrationTaskTest {
{ 3, 1, -1, 4},
}});
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on the 3rd
@ -204,7 +204,7 @@ public class GridSizeMigrationTaskTest {
{ 5, 2, -1, 6},
}});
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@ -235,7 +235,7 @@ public class GridSizeMigrationTaskTest {
{ 5, 2, 7, -1},
}}, 0);
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 4)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@ -262,7 +262,7 @@ public class GridSizeMigrationTaskTest {
{ 5, 6, 7, -1},
}}, 0);
new GridSizeMigrationTask(mContext, mDb, mValidPackages,
new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
new Point(4, 4), new Point(3, 3)).migrateWorkspace();
// Items in the second column of the first screen should get placed on a new screen.
@ -282,7 +282,7 @@ public class GridSizeMigrationTaskTest {
* represent the workspace grid.
*/
private void verifyWorkspace(int[][][] ids) {
IntArray allScreens = getWorkspaceScreenIds(mDb);
IntArray allScreens = getWorkspaceScreenIds(mDb, LauncherSettings.Favorites.TABLE_NAME);
assertEquals(ids.length, allScreens.size());
int total = 0;
@ -351,7 +351,7 @@ public class GridSizeMigrationTaskTest {
private final LinkedList<Point> mPoints;
public MultiStepMigrationTaskVerifier(int... points) {
super(null, null, null);
super(null, null, null, false);
mPoints = new LinkedList<>();
for (int i = 0; i < points.length; i += 2) {

View File

@ -52,6 +52,7 @@ import android.os.Process;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.util.Executors;
import com.android.launcher3.util.LauncherRoboTestRunner;
@ -91,7 +92,7 @@ public class LoaderCursorTest {
SCREEN, CELLX, CELLY, RESTORED, INTENT
});
mLoaderCursor = new LoaderCursor(mCursor, mApp);
mLoaderCursor = new LoaderCursor(mCursor, LauncherSettings.Favorites.CONTENT_URI, mApp);
mLoaderCursor.allUsers.put(0, Process.myUserHandle());
}

View File

@ -47,6 +47,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
import com.android.launcher3.util.DefaultDisplay.Info;
@ -156,6 +157,11 @@ public class InvariantDeviceProfile {
@TargetApi(23)
private InvariantDeviceProfile(Context context) {
if (context instanceof LauncherPreviewRenderer.PreviewContext) {
throw new IllegalArgumentException(
"PreviewContext is passed into this IDP constructor");
}
String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
: null;

View File

@ -27,6 +27,8 @@ import android.content.pm.LauncherApps;
import android.os.Handler;
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
@ -55,12 +57,12 @@ public class LauncherAppState {
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final SecureSettingsObserver mNotificationDotsObserver;
private final InstallSessionTracker mInstallSessionTracker;
private final SimpleBroadcastReceiver mModelChangeReceiver;
private final SafeCloseable mCalendarChangeTracker;
private final SafeCloseable mUserChangeListener;
private SecureSettingsObserver mNotificationDotsObserver;
private InstallSessionTracker mInstallSessionTracker;
private SimpleBroadcastReceiver mModelChangeReceiver;
private SafeCloseable mCalendarChangeTracker;
private SafeCloseable mUserChangeListener;
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
@ -74,15 +76,8 @@ public class LauncherAppState {
return mContext;
}
private LauncherAppState(Context context) {
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
mContext = context;
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
public LauncherAppState(Context context) {
this(context, LauncherFiles.APP_ICONS_DB);
mModelChangeReceiver = new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
@ -123,6 +118,17 @@ public class LauncherAppState {
}
}
public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
Log.v(Launcher.TAG, "LauncherAppState initiated");
Preconditions.assertUIThread();
mContext = context;
mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
}
protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
if (areNotificationDotsEnabled) {
NotificationListener.requestRebind(new ComponentName(
@ -148,11 +154,19 @@ public class LauncherAppState {
* Call from Application.onTerminate(), which is not guaranteed to ever be called.
*/
public void onTerminate() {
mContext.unregisterReceiver(mModelChangeReceiver);
if (mModelChangeReceiver != null) {
mContext.unregisterReceiver(mModelChangeReceiver);
}
mContext.getSystemService(LauncherApps.class).unregisterCallback(mModel);
mInstallSessionTracker.unregister();
mCalendarChangeTracker.close();
mUserChangeListener.close();
if (mInstallSessionTracker != null) {
mInstallSessionTracker.unregister();
}
if (mCalendarChangeTracker != null) {
mCalendarChangeTracker.close();
}
if (mUserChangeListener != null) {
mUserChangeListener.close();
}
CustomWidgetManager.INSTANCE.get(mContext).setWidgetRefreshCallback(null);
if (mNotificationDotsObserver != null) {

View File

@ -93,15 +93,26 @@ public class LauncherSettings {
public static final String TABLE_NAME = "favorites";
/**
* Backup table created when when the favorites table is modified during grid migration
* Backup table created when the favorites table is modified during grid migration
*/
public static final String BACKUP_TABLE_NAME = "favorites_bakup";
/**
* The content:// style URL for this table
* Temporary table used specifically for grid migrations during wallpaper preview
*/
public static final Uri CONTENT_URI = Uri.parse("content://" +
LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
public static final String PREVIEW_TABLE_NAME = "favorites_preview";
/**
* The content:// style URL for "favorites" table
*/
public static final Uri CONTENT_URI = Uri.parse("content://"
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
/**
* The content:// style URL for "favorites_preview" table
*/
public static final Uri PREVIEW_CONTENT_URI = Uri.parse("content://"
+ LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
/**
* The content:// style URL for a given row, identified by its id.
@ -111,8 +122,8 @@ public class LauncherSettings {
* @return The unique content URL for the specified row.
*/
public static Uri getContentUri(int id) {
return Uri.parse("content://" + LauncherProvider.AUTHORITY +
"/" + TABLE_NAME + "/" + id);
return Uri.parse("content://" + LauncherProvider.AUTHORITY
+ "/" + TABLE_NAME + "/" + id);
}
/**

View File

@ -20,14 +20,19 @@ import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.View.VISIBLE;
import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER;
import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
import static com.android.launcher3.model.GridSizeMigrationTask.needsToMigrate;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.annotation.TargetApi;
import android.app.Fragment;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@ -70,17 +75,31 @@ import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.icons.BaseIconFactory;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.GridSizeMigrationTask;
import com.android.launcher3.model.GridSizeMigrationTaskV2;
import com.android.launcher3.model.LoaderResults;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@ -101,6 +120,81 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
private static final String TAG = "LauncherPreviewRenderer";
/**
* Context used just for preview. It also provides a few objects (e.g. UserCache) just for
* preview purposes.
*/
public static class PreviewContext extends ContextWrapper {
private static final Set<MainThreadInitializedObject> WHITELIST = new HashSet<>(
Arrays.asList(UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE));
private final InvariantDeviceProfile mIdp;
private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
private final ConcurrentLinkedQueue<LauncherIconsForPreview> mIconPool =
new ConcurrentLinkedQueue<>();
public PreviewContext(Context base, InvariantDeviceProfile idp) {
super(base);
mIdp = idp;
}
@Override
public Context getApplicationContext() {
return this;
}
/**
* Find a cached object from mObjectMap if we have already created one. If not, generate
* an object using the provider.
*/
public <T> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
MainThreadInitializedObject.ObjectProvider<T> provider) {
if (!WHITELIST.contains(mainThreadInitializedObject)) {
throw new IllegalStateException("Leaking unknown objects");
}
if (mainThreadInitializedObject == LauncherAppState.INSTANCE) {
throw new IllegalStateException(
"Should not use MainThreadInitializedObject to initialize this with "
+ "PreviewContext");
}
if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) {
return (T) mIdp;
}
if (mObjectMap.containsKey(mainThreadInitializedObject)) {
return (T) mObjectMap.get(mainThreadInitializedObject);
}
T t = provider.get(this);
mObjectMap.put(mainThreadInitializedObject, t);
return t;
}
public LauncherIcons newLauncherIcons(Context context, boolean shapeDetection) {
LauncherIconsForPreview launcherIconsForPreview = mIconPool.poll();
if (launcherIconsForPreview != null) {
return launcherIconsForPreview;
}
return new LauncherIconsForPreview(context, mIdp.fillResIconDpi, mIdp.iconBitmapSize,
-1 /* poolId */, shapeDetection);
}
private final class LauncherIconsForPreview extends LauncherIcons {
private LauncherIconsForPreview(Context context, int fillResIconDpi, int iconBitmapSize,
int poolId, boolean shapeDetection) {
super(context, fillResIconDpi, iconBitmapSize, poolId, shapeDetection);
}
@Override
public void recycle() {
// Clear any temporary state variables
clear();
mIconPool.offer(this);
}
}
}
private final Handler mUiHandler;
private final Context mContext;
private final InvariantDeviceProfile mIdp;
@ -282,15 +376,28 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
private void renderScreenShot(Canvas canvas) {
if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
final LauncherModel launcherModel = LauncherAppState.getInstance(
mContext).getModel();
final WorkspaceItemsInfoFetcher fetcher = new WorkspaceItemsInfoFetcher();
launcherModel.enqueueModelUpdateTask(fetcher);
WorkspaceResult workspaceResult;
try {
workspaceResult = fetcher.mTask.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Log.d(TAG, "Error fetching workspace items info", e);
boolean needsToMigrate = needsToMigrate(mContext, mIdp);
boolean success = false;
if (needsToMigrate) {
success = MULTI_DB_GRID_MIRATION_ALGO.get()
? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
: GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
}
WorkspaceFetcher fetcher;
if (needsToMigrate && success) {
LauncherAppState appForPreview = new LauncherAppState(
new PreviewContext(mContext, mIdp), null /* iconCacheFileName */);
fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview);
MODEL_EXECUTOR.execute(fetcher);
} else {
fetcher = new WorkspaceItemsInfoFetcher();
LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask(
(LauncherModel.ModelUpdateTask) fetcher);
}
WorkspaceResult workspaceResult = fetcher.get();
if (workspaceResult == null) {
return;
}
@ -379,8 +486,13 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
}
}
private static class WorkspaceItemsInfoFetcher implements Callable<WorkspaceResult>,
LauncherModel.ModelUpdateTask {
private static void measureView(View view, int width, int height) {
view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
view.layout(0, 0, width, height);
}
private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask,
WorkspaceFetcher {
private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
@ -398,6 +510,11 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
mAllAppsList = allAppsList;
}
@Override
public FutureTask<WorkspaceResult> getTask() {
return mTask;
}
@Override
public void run() {
mTask.run();
@ -417,9 +534,45 @@ public class LauncherPreviewRenderer implements Callable<Bitmap> {
}
}
private static void measureView(View view, int width, int height) {
view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY));
view.layout(0, 0, width, height);
private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements
WorkspaceFetcher {
private final FutureTask<WorkspaceResult> mTask = new FutureTask<>(this);
WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
super(app, null, new BgDataModel(), null);
}
@Override
public FutureTask<WorkspaceResult> getTask() {
return mTask;
}
@Override
public void run() {
mTask.run();
}
@Override
public WorkspaceResult call() throws Exception {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI);
return new WorkspaceResult(mBgDataModel.workspaceItems, mBgDataModel.appWidgets,
mBgDataModel.widgetsModel);
}
}
private interface WorkspaceFetcher extends Runnable, Callable<WorkspaceResult> {
FutureTask<WorkspaceResult> getTask();
default WorkspaceResult get() {
try {
return getTask().get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
Log.d(TAG, "Error fetching workspace items info", e);
return null;
}
}
}
private static class WorkspaceResult {

View File

@ -81,9 +81,13 @@ public class IconCache extends BaseIconCache {
private int mPendingIconRequestCount = 0;
public IconCache(Context context, InvariantDeviceProfile inv) {
super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
public IconCache(Context context, InvariantDeviceProfile idp) {
this(context, idp, LauncherFiles.APP_ICONS_DB);
}
public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
mShortcutCachingLogic = new ShortcutCachingLogic();

View File

@ -19,8 +19,8 @@ package com.android.launcher3.icons;
import android.content.Context;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
/**
* Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
@ -41,6 +41,11 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
* avoid allocating new objects in many cases.
*/
public static LauncherIcons obtain(Context context, boolean shapeDetection) {
if (context instanceof LauncherPreviewRenderer.PreviewContext) {
return ((LauncherPreviewRenderer.PreviewContext) context).newLauncherIcons(context,
shapeDetection);
}
int poolId;
synchronized (sPoolSync) {
if (sPool != null) {
@ -52,7 +57,7 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
poolId = sPoolId;
}
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
return new LauncherIcons(context, idp.fillResIconDpi, idp.iconBitmapSize, poolId,
shapeDetection);
}
@ -68,7 +73,7 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
private LauncherIcons next;
private LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, int poolId,
boolean shapeDetection) {
super(context, fillResIconDpi, iconBitmapSize, shapeDetection);
mPoolId = poolId;

View File

@ -100,12 +100,24 @@ public class GridBackupTable {
Process.myUserHandle()), 0);
return false;
}
return restoreIfBackupExists(Favorites.TABLE_NAME);
}
public boolean restoreToPreviewIfBackupExists() {
if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
return false;
}
return restoreIfBackupExists(Favorites.PREVIEW_TABLE_NAME);
}
private boolean restoreIfBackupExists(String toTableName) {
if (loadDBProperties() != STATE_SANITIZED) {
return false;
}
long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
Process.myUserHandle());
copyTable(mDb, BACKUP_TABLE_NAME, Favorites.TABLE_NAME, userSerial);
copyTable(mDb, BACKUP_TABLE_NAME, toTableName, userSerial);
Log.d(TAG, "Backup table found");
return true;
}

View File

@ -3,6 +3,7 @@ package com.android.launcher3.model;
import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
import static com.android.launcher3.Utilities.getPointString;
import static com.android.launcher3.Utilities.parsePoint;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
import android.content.ComponentName;
import android.content.ContentValues;
@ -14,6 +15,7 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
@ -29,6 +31,7 @@ import com.android.launcher3.LauncherSettings.Settings;
import com.android.launcher3.Utilities;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.LauncherPreviewRenderer;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@ -69,6 +72,7 @@ public class GridSizeMigrationTask {
private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
private final HashSet<String> mValidPackages;
private final String mTableName;
private final int mSrcX, mSrcY;
private final int mTrgX, mTrgY;
@ -78,10 +82,12 @@ public class GridSizeMigrationTask {
private final int mDestHotseatSize;
protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
HashSet<String> validPackages, Point sourceSize, Point targetSize) {
HashSet<String> validPackages, boolean usePreviewTable, Point sourceSize,
Point targetSize) {
mContext = context;
mDb = db;
mValidPackages = validPackages;
mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
mSrcX = sourceSize.x;
mSrcY = sourceSize.y;
@ -97,10 +103,12 @@ public class GridSizeMigrationTask {
}
protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
HashSet<String> validPackages, int srcHotseatSize, int destHotseatSize) {
HashSet<String> validPackages, boolean usePreviewTable, int srcHotseatSize,
int destHotseatSize) {
mContext = context;
mDb = db;
mValidPackages = validPackages;
mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
mSrcHotseatSize = srcHotseatSize;
@ -120,7 +128,7 @@ public class GridSizeMigrationTask {
// Update items
int updateCount = mUpdateOperations.size();
for (int i = 0; i < updateCount; i++) {
mDb.update(Favorites.TABLE_NAME, mUpdateOperations.valueAt(i),
mDb.update(mTableName, mUpdateOperations.valueAt(i),
"_id=" + mUpdateOperations.keyAt(i), null);
}
@ -128,8 +136,8 @@ public class GridSizeMigrationTask {
if (DEBUG) {
Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
}
mDb.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
Favorites._ID, mEntryToRemove), null);
mDb.delete(mTableName, Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove),
null);
}
return updateCount > 0 || !mEntryToRemove.isEmpty();
@ -182,8 +190,8 @@ public class GridSizeMigrationTask {
}
@VisibleForTesting
static IntArray getWorkspaceScreenIds(SQLiteDatabase db) {
return LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME, Favorites.SCREEN,
static IntArray getWorkspaceScreenIds(SQLiteDatabase db, String tableName) {
return LauncherDbUtils.queryIntArray(db, tableName, Favorites.SCREEN,
Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
Favorites.SCREEN, Favorites.SCREEN);
}
@ -192,7 +200,7 @@ public class GridSizeMigrationTask {
* @return true if any DB change was made
*/
protected boolean migrateWorkspace() throws Exception {
IntArray allScreens = getWorkspaceScreenIds(mDb);
IntArray allScreens = getWorkspaceScreenIds(mDb, mTableName);
if (allScreens.isEmpty()) {
throw new Exception("Unable to get workspace screens");
}
@ -244,12 +252,12 @@ public class GridSizeMigrationTask {
/**
* Migrate a particular screen id.
* Strategy:
* 1) For all possible combinations of row and column, pick the one which causes the least
* data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
* 2) Maintain a list of all lost items before this screen, and add any new item lost from
* this screen to that list as well.
* 3) If all those items from the above list can be placed on this screen, place them
* (otherwise they are placed on a new screen).
* 1) For all possible combinations of row and column, pick the one which causes the least
* data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
* 2) Maintain a list of all lost items before this screen, and add any new item lost from
* this screen to that list as well.
* 3) If all those items from the above list can be placed on this screen, place them
* (otherwise they are placed on a new screen).
*/
protected void migrateScreen(int screenId) {
// If we are migrating the first screen, do not touch the first row.
@ -362,9 +370,9 @@ public class GridSizeMigrationTask {
/**
* Tries the remove the provided row and column.
*
* @param items all the items on the screen under operation
* @param items all the items on the screen under operation
* @param outLoss array of size 2. The first entry is filled with weight loss, and the second
* with the overall item movement.
* with the overall item movement.
*/
private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
ArrayList<DbEntry> items, float[] outLoss) {
@ -379,13 +387,13 @@ public class GridSizeMigrationTask {
for (DbEntry item : items) {
if ((item.cellX <= col && (item.spanX + item.cellX) > col)
|| (item.cellY <= row && (item.spanY + item.cellY) > row)) {
|| (item.cellY <= row && (item.spanY + item.cellY) > row)) {
removedItems.add(item);
if (item.cellX >= col) item.cellX --;
if (item.cellY >= row) item.cellY --;
if (item.cellX >= col) item.cellX--;
if (item.cellY >= row) item.cellY--;
} else {
if (item.cellX > col) item.cellX --;
if (item.cellY > row) item.cellY --;
if (item.cellX > col) item.cellX--;
if (item.cellY > row) item.cellY--;
finalItems.add(item);
occupied.markCells(item, true);
}
@ -438,9 +446,9 @@ public class GridSizeMigrationTask {
/**
* Recursively finds a placement for the provided items.
*
* @param index the position in {@link #itemsToPlace} to start looking at.
* @param weightLoss total weight loss upto this point
* @param moveCost total move cost upto this point
* @param index the position in {@link #itemsToPlace} to start looking at.
* @param weightLoss total weight loss upto this point
* @param moveCost total move cost upto this point
* @param itemsPlaced all the items already placed upto this point
*/
public void find(int index, float weightLoss, float moveCost,
@ -481,11 +489,11 @@ public class GridSizeMigrationTask {
float newMoveCost = moveCost;
if (x != myX) {
me.cellX = x;
newMoveCost ++;
newMoveCost++;
}
if (y != myY) {
me.cellY = y;
newMoveCost ++;
newMoveCost++;
}
if (ignoreMove) {
newMoveCost = moveCost;
@ -500,35 +508,35 @@ public class GridSizeMigrationTask {
// Try resizing horizontally
if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
me.spanX --;
me.spanX--;
occupied.markCells(me, true);
// 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
occupied.markCells(me, false);
me.spanX ++;
me.spanX++;
}
// Try resizing vertically
if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
me.spanY --;
me.spanY--;
occupied.markCells(me, true);
// 1 extra move cost
find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
occupied.markCells(me, false);
me.spanY ++;
me.spanY++;
}
// Try resizing horizontally & vertically
if (myH > me.minSpanY && myW > me.minSpanX &&
occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
me.spanX --;
me.spanY --;
me.spanX--;
me.spanY--;
occupied.markCells(me, true);
// 2 extra move cost
find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
occupied.markCells(me, false);
me.spanX ++;
me.spanY ++;
me.spanX++;
me.spanY++;
}
me.cellX = myX;
me.cellY = myY;
@ -565,11 +573,11 @@ public class GridSizeMigrationTask {
float newMoveCost = moveCost;
if (newX != myX) {
me.cellX = newX;
newMoveCost ++;
newMoveCost++;
}
if (newY != myY) {
me.cellY = newY;
newMoveCost ++;
newMoveCost++;
}
if (ignoreMove) {
newMoveCost = moveCost;
@ -602,7 +610,7 @@ public class GridSizeMigrationTask {
}
private ArrayList<DbEntry> loadHotseatEntries() {
Cursor c = queryWorkspace(
Cursor c = queryWorkspace(
new String[]{
Favorites._ID, // 0
Favorites.ITEM_TYPE, // 1
@ -787,7 +795,7 @@ public class GridSizeMigrationTask {
}
protected Cursor queryWorkspace(String[] columns, String where) {
return mDb.query(Favorites.TABLE_NAME, columns, where, null, null, null, null);
return mDb.query(mTableName, columns, where, null, null, null, null);
}
/**
@ -879,24 +887,44 @@ public class GridSizeMigrationTask {
}
/**
* Migrates the workspace and hotseat in case their sizes changed.
* Check given a new IDP, if migration is necessary.
*/
public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
SharedPreferences prefs = Utilities.getPrefs(context);
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
|| idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
idp.numHotseatIcons);
}
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
public static boolean migrateGridIfNeeded(Context context) {
if (context instanceof LauncherPreviewRenderer.PreviewContext) {
return true;
}
return migrateGridIfNeeded(context, null);
}
/**
* Run the migration algorithm if needed. For preview, we provide the intended idp because it
* has not been changed. If idp is null, we read it from the context, for actual grid migration.
*
* @return false if the migration failed.
*/
public static boolean migrateGridIfNeeded(Context context) {
SharedPreferences prefs = Utilities.getPrefs(context);
InvariantDeviceProfile idp = LauncherAppState.getIDP(context);
public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
boolean migrateForPreview = idp != null;
if (!migrateForPreview) {
idp = LauncherAppState.getIDP(context);
}
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, "")) &&
idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
idp.numHotseatIcons)) {
// Skip if workspace and hotseat sizes have not changed.
if (!needsToMigrate(context, idp)) {
return true;
}
long migrationStartTime = System.currentTimeMillis();
SharedPreferences prefs = Utilities.getPrefs(context);
String gridSizeString = getPointString(idp.numColumns, idp.numRows);
long migrationStartTime = SystemClock.elapsedRealtime();
try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
.getBinder(Settings.EXTRA_VALUE)) {
@ -907,33 +935,39 @@ public class GridSizeMigrationTask {
KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
boolean dbChanged = false;
if (migrateForPreview) {
copyTable(transaction.getDb(), Favorites.TABLE_NAME, Favorites.PREVIEW_TABLE_NAME,
context);
}
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
srcHotseatCount, sourceSize.x, sourceSize.y);
if (backupTable.backupOrRestoreAsNeeded()) {
if (migrateForPreview ? backupTable.restoreToPreviewIfBackupExists()
: backupTable.backupOrRestoreAsNeeded()) {
dbChanged = true;
srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
}
HashSet<String> validPackages = getValidPackages(context);
// Hotseat
// Hotseat.
if (srcHotseatCount != idp.numHotseatIcons) {
// Migrate hotseat.
dbChanged = new GridSizeMigrationTask(context, transaction.getDb(),
validPackages, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
dbChanged = new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
migrateForPreview, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
}
// Grid size
Point targetSize = new Point(idp.numColumns, idp.numRows);
if (new MultiStepMigrationTask(validPackages, context, transaction.getDb())
.migrate(sourceSize, targetSize)) {
if (new MultiStepMigrationTask(validPackages, context, transaction.getDb(),
migrateForPreview).migrate(sourceSize, targetSize)) {
dbChanged = true;
}
if (dbChanged) {
// Make sure we haven't removed everything.
final Cursor c = context.getContentResolver().query(
Favorites.CONTENT_URI, null, null, null, null);
migrateForPreview ? Favorites.PREVIEW_CONTENT_URI : Favorites.CONTENT_URI,
null, null, null, null);
boolean hasData = c.moveToNext();
c.close();
if (!hasData) {
@ -942,21 +976,25 @@ public class GridSizeMigrationTask {
}
transaction.commit();
Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
if (!migrateForPreview) {
Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
}
return true;
} catch (Exception e) {
Log.e(TAG, "Error during grid migration", e);
Log.e(TAG, "Error during preview grid migration", e);
return false;
} finally {
Log.v(TAG, "Workspace migration completed in "
+ (System.currentTimeMillis() - migrationStartTime));
Log.v(TAG, "Preview workspace migration completed in "
+ (SystemClock.elapsedRealtime() - migrationStartTime));
// Save current configuration, so that the migration does not run again.
prefs.edit()
.putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
.putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
.apply();
if (!migrateForPreview) {
// Save current configuration, so that the migration does not run again.
prefs.edit()
.putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
.putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
.apply();
}
}
}
@ -989,7 +1027,7 @@ public class GridSizeMigrationTask {
.getBinder(Settings.EXTRA_VALUE)) {
GridSizeMigrationTask task = new GridSizeMigrationTask(
context, transaction.getDb(), getValidPackages(context),
Integer.MAX_VALUE, Integer.MAX_VALUE);
false /* usePreviewTable */, Integer.MAX_VALUE, Integer.MAX_VALUE);
// Load all the valid entries
ArrayList<DbEntry> items = task.loadHotseatEntries();
@ -1011,12 +1049,14 @@ public class GridSizeMigrationTask {
private final HashSet<String> mValidPackages;
private final Context mContext;
private final SQLiteDatabase mDb;
private final boolean mUsePreviewTable;
public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
SQLiteDatabase db) {
SQLiteDatabase db, boolean usePreviewTable) {
mValidPackages = validPackages;
mContext = context;
mDb = db;
mUsePreviewTable = usePreviewTable;
}
public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
@ -1052,8 +1092,8 @@ public class GridSizeMigrationTask {
}
protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
return new GridSizeMigrationTask(mContext, mDb,
mValidPackages, sourceSize, nextSize).migrateWorkspace();
return new GridSizeMigrationTask(mContext, mDb, mValidPackages, mUsePreviewTable,
sourceSize, nextSize).migrateWorkspace();
}
}
}

View File

@ -18,6 +18,8 @@ package com.android.launcher3.model;
import android.content.Context;
import com.android.launcher3.InvariantDeviceProfile;
/**
* This class takes care of shrinking the workspace (by maximum of one row and one column), as a
* result of restoring from a larger device or device density change.
@ -28,13 +30,20 @@ public class GridSizeMigrationTaskV2 {
}
/**
* Migrates the workspace and hotseat in case their sizes changed.
*
* @return false if the migration failed.
*/
/** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
public static boolean migrateGridIfNeeded(Context context) {
// To be implemented.
return true;
}
/**
* Run the migration algorithm if needed. For preview, we provide the intended idp because it
* has not been changed. If idp is null, we read it from the context, for actual grid migration.
*
* @return false if the migration failed.
*/
public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
// To be implemented.
return true;
}
}

View File

@ -28,6 +28,7 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.net.Uri;
import android.os.UserHandle;
import android.provider.BaseColumns;
import android.text.TextUtils;
@ -64,6 +65,7 @@ public class LoaderCursor extends CursorWrapper {
public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>();
private final Uri mContentUri;
private final Context mContext;
private final PackageManager mPM;
private final IconCache mIconCache;
@ -96,8 +98,10 @@ public class LoaderCursor extends CursorWrapper {
public int itemType;
public int restoreFlag;
public LoaderCursor(Cursor c, LauncherAppState app) {
super(c);
public LoaderCursor(Cursor cursor, Uri contentUri, LauncherAppState app) {
super(cursor);
mContentUri = contentUri;
mContext = app.getContext();
mIconCache = app.getIconCache();
mIDP = app.getInvariantDeviceProfile();
@ -312,9 +316,8 @@ public class LoaderCursor extends CursorWrapper {
public boolean commitDeleted() {
if (itemsToRemove.size() > 0) {
// Remove dead items
mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI,
Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, itemsToRemove), null);
mContext.getContentResolver().delete(mContentUri, Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, itemsToRemove), null);
return true;
}
return false;
@ -339,7 +342,7 @@ public class LoaderCursor extends CursorWrapper {
// Update restored items that no longer require special handling
ContentValues values = new ContentValues();
values.put(LauncherSettings.Favorites.RESTORED, 0);
mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values,
mContext.getContentResolver().update(mContentUri, values,
Utilities.createDbSelectionQuery(
LauncherSettings.Favorites._ID, restoredRows), null);
}

View File

@ -36,6 +36,7 @@ import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.ShortcutInfo;
import android.net.Uri;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
@ -76,7 +77,6 @@ import com.android.launcher3.qsb.QsbContainerView;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@ -106,7 +106,7 @@ public class LoaderTask implements Runnable {
private final LauncherAppState mApp;
private final AllAppsList mBgAllAppsList;
private final BgDataModel mBgDataModel;
protected final BgDataModel mBgDataModel;
private FirstScreenBroadcast mFirstScreenBroadcast;
@ -284,6 +284,10 @@ public class LoaderTask implements Runnable {
@VisibleForTesting
void loadWorkspace(List<ShortcutInfo> allDeepShortcuts) {
loadWorkspace(allDeepShortcuts, LauncherSettings.Favorites.CONTENT_URI);
}
protected void loadWorkspace(List<ShortcutInfo> allDeepShortcuts, Uri contentUri) {
final Context context = mApp.getContext();
final ContentResolver contentResolver = context.getContentResolver();
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
@ -327,8 +331,8 @@ public class LoaderTask implements Runnable {
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(contentResolver.query(
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
final LoaderCursor c = new LoaderCursor(
contentResolver.query(contentUri, null, null, null, null), contentUri, mApp);
Map<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null;

View File

@ -22,10 +22,12 @@ import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.os.Binder;
import android.os.Process;
import android.util.Log;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.util.IntArray;
import java.util.Locale;
@ -116,6 +118,15 @@ public class LauncherDbUtils {
db.execSQL("DROP TABLE IF EXISTS " + tableName);
}
/** Copy from table to the to table. */
public static void copyTable(SQLiteDatabase db, String from, String to, Context context) {
long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
Process.myUserHandle());
dropTable(db, to);
Favorites.addTableToDb(db, userSerial, false, to);
db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
}
/**
* Utility class to simplify managing sqlite transactions
*/

View File

@ -18,7 +18,6 @@ package com.android.launcher3.provider;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Point;
@ -43,7 +42,7 @@ public class LossyScreenMigrationTask extends GridSizeMigrationTask {
protected LossyScreenMigrationTask(
Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
// Decrease the rows count by 1
super(context, db, getValidPackages(context),
super(context, db, getValidPackages(context), false /* usePreviewTable */,
new Point(idp.numColumns, idp.numRows + 1),
new Point(idp.numColumns, idp.numRows));

View File

@ -22,6 +22,7 @@ import android.os.Looper;
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
import com.android.launcher3.util.ResourceBasedOverride.Overrides;
import java.util.concurrent.ExecutionException;
@ -39,6 +40,10 @@ public class MainThreadInitializedObject<T> {
}
public T get(Context context) {
if (context instanceof PreviewContext) {
return ((PreviewContext) context).getObject(this, mProvider);
}
if (mValue == null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
mValue = TraceHelper.whitelistIpcs("main.thread.object",