Merge "Render user's actual workspace in ThemePicker preview (Part 3)" into ub-launcher3-master

This commit is contained in:
Tracy Zhou 2020-02-24 20:01:58 +00:00 committed by Android (Google) Code Review
commit 4365734017
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",