Merge "Cleanup ENABLE_TASKBAR_EDU_TOOLTIP: remove EDU sheet." into udc-dev
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 2e1318b..bc97df1 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -48,11 +48,15 @@
import android.util.Log;
import android.util.StatsEvent;
+import androidx.annotation.AnyThread;
+import androidx.annotation.CallSuper;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.InstanceIdSequence;
@@ -62,6 +66,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.PersistedItemArray;
import com.android.quickstep.logging.SettingsChangeLogger;
@@ -111,45 +116,80 @@
mStatsManager = context.getSystemService(StatsManager.class);
}
+ @CallSuper
@Override
- public void loadHotseatItems(UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
- // TODO: Implement caching and preloading
- super.loadHotseatItems(ums, pinnedShortcuts);
-
- WorkspaceItemFactory hotseatFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
- mIDP.numDatabaseHotseatIcons, mHotseatState.containerId);
- FixedContainerItems hotseatItems = new FixedContainerItems(mHotseatState.containerId,
- mHotseatState.storage.read(mApp.getContext(), hotseatFactory, ums.allUsers::get));
- mDataModel.extraItems.put(mHotseatState.containerId, hotseatItems);
+ public void loadAndBindWorkspaceItems(@NonNull UserManagerState ums,
+ @NonNull BgDataModel.Callbacks[] callbacks,
+ @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
+ loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseHotseatIcons,
+ mHotseatState);
}
+ @CallSuper
@Override
- public void loadAllAppsItems(UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
- // TODO: Implement caching and preloading
- super.loadAllAppsItems(ums, pinnedShortcuts);
-
- WorkspaceItemFactory allAppsFactory = new WorkspaceItemFactory(mApp, ums, pinnedShortcuts,
- mIDP.numDatabaseAllAppsColumns, mAllAppsState.containerId);
- FixedContainerItems allAppsPredictionItems = new FixedContainerItems(
- mAllAppsState.containerId, mAllAppsState.storage.read(mApp.getContext(),
- allAppsFactory, ums.allUsers::get));
- mDataModel.extraItems.put(mAllAppsState.containerId, allAppsPredictionItems);
+ public void loadAndBindAllAppsItems(@NonNull UserManagerState ums,
+ @NonNull BgDataModel.Callbacks[] callbacks,
+ @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) {
+ loadAndBindItems(ums, pinnedShortcuts, callbacks, mIDP.numDatabaseAllAppsColumns,
+ mAllAppsState);
}
- @Override
- public void loadWidgetsRecommendationItems() {
+ @WorkerThread
+ private void loadAndBindItems(@NonNull UserManagerState ums,
+ @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
+ @NonNull BgDataModel.Callbacks[] callbacks,
+ int numColumns, @NonNull PredictorState state) {
// TODO: Implement caching and preloading
- super.loadWidgetsRecommendationItems();
+
+ WorkspaceItemFactory factory =
+ new WorkspaceItemFactory(mApp, ums, pinnedShortcuts, numColumns, state.containerId);
+ FixedContainerItems fci = new FixedContainerItems(state.containerId,
+ state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ bindPredictionItems(callbacks, fci);
+ }
+ mDataModel.extraItems.put(state.containerId, fci);
+ }
+
+ @CallSuper
+ @Override
+ public void loadAndBindOtherItems(@NonNull BgDataModel.Callbacks[] callbacks) {
+ FixedContainerItems widgetPredictionFCI = new FixedContainerItems(
+ mWidgetsRecommendationState.containerId, new ArrayList<>());
// Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
- mDataModel.extraItems.put(mWidgetsRecommendationState.containerId,
- new FixedContainerItems(mWidgetsRecommendationState.containerId,
- new ArrayList<>()));
+ mDataModel.extraItems.put(mWidgetsRecommendationState.containerId, widgetPredictionFCI);
+
+ bindPredictionItems(callbacks, widgetPredictionFCI);
+ loadStringCache(mDataModel.stringCache);
+ }
+
+ @AnyThread
+ private void bindPredictionItems(@NonNull BgDataModel.Callbacks[] callbacks,
+ @NonNull FixedContainerItems fci) {
+ Executors.MAIN_EXECUTOR.execute(() -> {
+ for (BgDataModel.Callbacks c : callbacks) {
+ c.bindExtraContainerItems(fci);
+ }
+ });
}
@Override
+ @WorkerThread
+ public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) {
+ Iterable<FixedContainerItems> containerItems;
+ synchronized (mDataModel.extraItems) {
+ containerItems = mDataModel.extraItems.clone();
+ }
+ Executors.MAIN_EXECUTOR.execute(() -> {
+ for (BgDataModel.Callbacks c : callbacks) {
+ for (FixedContainerItems fci : containerItems) {
+ c.bindExtraContainerItems(fci);
+ }
+ }
+ });
+ }
+
public void markActive() {
super.markActive();
mActive = true;
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index d8fd51a..003c2fc 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -47,11 +47,7 @@
import androidx.annotation.WorkerThread;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.RemoteActionShortcut;
import com.android.launcher3.popup.SystemShortcut;
@@ -73,21 +69,14 @@
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
private static final boolean DEBUG = false;
- private static final int UNKNOWN_MINIMAL_DEVICE_STATE = 0;
- private static final int IN_MINIMAL_DEVICE = 2;
-
// Welbeing contract
private static final String PATH_ACTIONS = "actions";
- private static final String PATH_MINIMAL_DEVICE = "minimal_device";
- private static final String METHOD_GET_MINIMAL_DEVICE_CONFIG = "get_minimal_device_config";
private static final String METHOD_GET_ACTIONS = "get_actions";
private static final String EXTRA_ACTIONS = "actions";
private static final String EXTRA_ACTION = "action";
private static final String EXTRA_MAX_NUM_ACTIONS_SHOWN = "max_num_actions_shown";
private static final String EXTRA_PACKAGES = "packages";
private static final String EXTRA_SUCCESS = "success";
- private static final String EXTRA_MINIMAL_DEVICE_STATE = "minimal_device_state";
- private static final String DB_NAME_MINIMAL_DEVICE = "minimal.db";
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
new MainThreadInitializedObject<>(WellbeingModel::new);
@@ -137,36 +126,7 @@
@WorkerThread
private void onWellbeingUriChanged(Uri uri) {
Preconditions.assertNonUiThread();
- if (DEBUG || mIsInTest) {
- Log.d(TAG, "ContentObserver.onChange() called with: uri = [" + uri + "]");
- }
- if (uri.getPath().contains(PATH_ACTIONS)) {
- // Wellbeing reports that app actions have changed.
- updateAllPackages();
- } else if (uri.getPath().contains(PATH_MINIMAL_DEVICE)) {
- // Wellbeing reports that minimal device state or config is changed.
- if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
- return;
- }
-
- // Temporary bug fix for b/169771796. Wellbeing provides the layout configuration when
- // minimal device is enabled. We always want to reload the configuration from Wellbeing
- // since the layout configuration might have changed.
- mContext.deleteDatabase(DB_NAME_MINIMAL_DEVICE);
-
- final Bundle extras = new Bundle();
- String dbFile;
- if (isInMinimalDeviceMode()) {
- dbFile = DB_NAME_MINIMAL_DEVICE;
- extras.putString(LauncherProvider.KEY_LAYOUT_PROVIDER_AUTHORITY,
- mWellbeingProviderPkg + ".api");
- } else {
- dbFile = InvariantDeviceProfile.INSTANCE.get(mContext).dbFile;
- }
- LauncherSettings.Settings.call(mContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_SWITCH_DATABASE,
- dbFile, extras);
- }
+ updateAllPackages();
}
public void setInTest(boolean inTest) {
@@ -178,12 +138,9 @@
final ContentResolver resolver = mContext.getContentResolver();
resolver.unregisterContentObserver(mContentObserver);
Uri actionsUri = apiBuilder().path(PATH_ACTIONS).build();
- Uri minimalDeviceUri = apiBuilder().path(PATH_MINIMAL_DEVICE).build();
try {
resolver.registerContentObserver(
actionsUri, true /* notifyForDescendants */, mContentObserver);
- resolver.registerContentObserver(
- minimalDeviceUri, true /* notifyForDescendants */, mContentObserver);
} catch (Exception e) {
Log.e(TAG, "Failed to register content observer for " + actionsUri + ": " + e);
if (mIsInTest) throw new RuntimeException(e);
@@ -228,32 +185,6 @@
}
@WorkerThread
- private boolean isInMinimalDeviceMode() {
- if (!FeatureFlags.ENABLE_MINIMAL_DEVICE.get()) {
- return false;
- }
- if (DEBUG || mIsInTest) {
- Log.d(TAG, "isInMinimalDeviceMode() called");
- }
- Preconditions.assertNonUiThread();
-
- final Uri contentUri = apiBuilder().build();
- try (ContentProviderClient client = mContext.getContentResolver()
- .acquireUnstableContentProviderClient(contentUri)) {
- final Bundle remoteBundle = client == null ? null : client.call(
- METHOD_GET_MINIMAL_DEVICE_CONFIG, null /* args */, null /* extras */);
- return remoteBundle != null
- && remoteBundle.getInt(EXTRA_MINIMAL_DEVICE_STATE,
- UNKNOWN_MINIMAL_DEVICE_STATE) == IN_MINIMAL_DEVICE;
- } catch (Exception e) {
- Log.e(TAG, "Failed to retrieve data from " + contentUri + ": " + e);
- if (mIsInTest) throw new RuntimeException(e);
- }
- if (DEBUG || mIsInTest) Log.i(TAG, "isInMinimalDeviceMode(): finished");
- return false;
- }
-
- @WorkerThread
private boolean updateActions(String[] packageNames) {
if (packageNames.length == 0) {
return true;
diff --git a/quickstep/src/com/android/quickstep/MultiStateCallback.java b/quickstep/src/com/android/quickstep/MultiStateCallback.java
index a68bea2..df42efc 100644
--- a/quickstep/src/com/android/quickstep/MultiStateCallback.java
+++ b/quickstep/src/com/android/quickstep/MultiStateCallback.java
@@ -86,9 +86,7 @@
Log.d(TAG, "[" + System.identityHashCode(this) + "] Adding "
+ convertToFlagNames(stateFlag) + " to " + convertToFlagNames(mState));
}
- if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
- trackGestureEvents(stateFlag);
- }
+ trackGestureEvents(stateFlag);
final int oldState = mState;
mState = mState | stateFlag;
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
index e05d85c..409bf9c 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java
@@ -155,15 +155,13 @@
}
public void dump(String prefix, PrintWriter writer) {
- if (FeatureFlags.ENABLE_GESTURE_ERROR_DETECTION.get()) {
- writer.println(prefix + "ActiveGestureErrorDetector:");
- for (int i = 0; i < logs.length; i++) {
- EventLog eventLog = logs[(nextIndex + i) % logs.length];
- if (eventLog == null) {
- continue;
- }
- ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog);
+ writer.println(prefix + "ActiveGestureErrorDetector:");
+ for (int i = 0; i < logs.length; i++) {
+ EventLog eventLog = logs[(nextIndex + i) % logs.length];
+ if (eventLog == null) {
+ continue;
}
+ ActiveGestureErrorDetector.analyseAndDump(prefix + '\t', writer, eventLog);
}
writer.println(prefix + "ActiveGestureLog history:");
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 2083726..5367d80 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -38,6 +38,7 @@
import android.util.Xml;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherProvider.SqlArguments;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -531,6 +532,7 @@
protected class SearchWidgetParser extends PendingWidgetParser {
@Override
@Nullable
+ @WorkerThread
public ComponentName getComponentName(XmlPullParser parser) {
return QsbContainerView.getSearchComponentName(mContext);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ad95f7e..06ac44a 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -421,6 +421,9 @@
launcherBinder.bindAllApps();
launcherBinder.bindDeepShortcuts();
launcherBinder.bindWidgets();
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mModelDelegate.bindAllModelExtras(callbacksList);
+ }
return true;
} else {
stopLoader();
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index e688709..f4892b2 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -18,11 +18,9 @@
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
-import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
import android.annotation.TargetApi;
-import android.app.backup.BackupManager;
import android.appwidget.AppWidgetManager;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -31,91 +29,59 @@
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
import android.content.OperationApplicationException;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
-import android.database.sqlite.SQLiteStatement;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Process;
-import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.BaseColumns;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Xml;
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.DbDowngradeHelper;
-import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.provider.LauncherDbUtils;
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
import com.android.launcher3.provider.RestoreDbTask;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.NoLocaleSQLiteHelper;
-import com.android.launcher3.util.PackageManagerHelper;
import com.android.launcher3.util.Partner;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.widget.LauncherWidgetHolder;
import org.xmlpull.v1.XmlPullParser;
-import java.io.File;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
-import java.net.URISyntaxException;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Locale;
-import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
public class LauncherProvider extends ContentProvider {
private static final String TAG = "LauncherProvider";
- private static final boolean LOGD = false;
-
- private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
- private static final long RESTORE_BACKUP_TABLE_DELAY = TimeUnit.SECONDS.toMillis(30);
-
- /**
- * Represents the schema of the database. Changes in scheme need not be backwards compatible.
- * When increasing the scheme version, ensure that downgrade_schema.json is updated
- */
- public static final int SCHEMA_VERSION = 31;
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".settings";
- public static final String KEY_LAYOUT_PROVIDER_AUTHORITY = "KEY_LAYOUT_PROVIDER_AUTHORITY";
private static final int TEST_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test_workspace;
private static final int TEST2_WORKSPACE_LAYOUT_RES_XML = R.xml.default_test2_workspace;
private static final int TAPL_WORKSPACE_LAYOUT_RES_XML = R.xml.default_tapl_test_workspace;
- static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+ public static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
protected DatabaseHelper mOpenHelper;
protected String mProviderAuthority;
- private long mLastRestoreTimestamp = 0L;
-
private int mDefaultWorkspaceLayoutOverride = 0;
/**
@@ -198,18 +164,6 @@
return result;
}
- @Thunk static int dbInsertAndCheck(DatabaseHelper helper,
- SQLiteDatabase db, String table, String nullColumnHack, ContentValues values) {
- if (values == null) {
- throw new RuntimeException("Error: attempting to insert null values");
- }
- if (!values.containsKey(LauncherSettings.Favorites._ID)) {
- throw new RuntimeException("Error: attempting to add item without specifying an id");
- }
- helper.checkId(values);
- return (int) db.insert(table, nullColumnHack, values);
- }
-
private void reloadLauncherIfExternal() {
if (Binder.getCallingPid() != Process.myPid()) {
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
@@ -233,7 +187,7 @@
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
addModifiedTime(initialValues);
- final int rowId = dbInsertAndCheck(mOpenHelper, db, args.table, null, initialValues);
+ final int rowId = mOpenHelper.dbInsertAndCheck(db, args.table, initialValues);
if (rowId < 0) return null;
onAddOrDeleteOp(db);
@@ -292,7 +246,7 @@
int numValues = values.length;
for (int i = 0; i < numValues; i++) {
addModifiedTime(values[i]);
- if (dbInsertAndCheck(mOpenHelper, db, args.table, null, values[i]) < 0) {
+ if (mOpenHelper.dbInsertAndCheck(db, args.table, values[i]) < 0) {
return 0;
}
}
@@ -374,13 +328,6 @@
clearFlagEmptyDbCreated();
return null;
}
- case LauncherSettings.Settings.METHOD_WAS_EMPTY_DB_CREATED : {
- Bundle result = new Bundle();
- result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
- LauncherPrefs.getPrefs(getContext()).getBoolean(
- mOpenHelper.getKey(EMPTY_DATABASE_CREATED), false));
- return result;
- }
case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
Bundle result = new Bundle();
result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE, deleteEmptyFolders()
@@ -438,25 +385,11 @@
new SQLiteTransaction(mOpenHelper.getWritableDatabase()));
return result;
}
- case LauncherSettings.Settings.METHOD_REFRESH_BACKUP_TABLE: {
- mOpenHelper.mBackupTableExists = tableExists(mOpenHelper.getReadableDatabase(),
- Favorites.BACKUP_TABLE_NAME);
- return null;
- }
case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
mOpenHelper.mHotseatRestoreTableExists = tableExists(
mOpenHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
return null;
}
- case LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE: {
- final long ts = System.currentTimeMillis();
- if (ts - mLastRestoreTimestamp > RESTORE_BACKUP_TABLE_DELAY) {
- mLastRestoreTimestamp = ts;
- RestoreDbTask.restoreIfPossible(
- getContext(), mOpenHelper, new BackupManager(getContext()));
- }
- return null;
- }
case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
Bundle result = new Bundle();
result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
@@ -479,22 +412,6 @@
() -> mOpenHelper));
return result;
}
- case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
- if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
- final DatabaseHelper helper = mOpenHelper;
- if (extras == null || !extras.containsKey(KEY_LAYOUT_PROVIDER_AUTHORITY)) {
- mProviderAuthority = null;
- } else {
- mProviderAuthority = extras.getString(KEY_LAYOUT_PROVIDER_AUTHORITY);
- }
- mOpenHelper = DatabaseHelper.createDatabaseHelper(
- getContext(), arg, false /* forMigration */);
- helper.close();
- LauncherAppState app = LauncherAppState.getInstanceNoCreate();
- if (app == null) return null;
- app.getModel().forceReload();
- return null;
- }
}
return null;
}
@@ -658,504 +575,6 @@
mOpenHelper, getContext().getResources(), defaultLayout);
}
- /**
- * The class is subclassed in tests to create an in-memory db.
- */
- public static class DatabaseHelper extends NoLocaleSQLiteHelper implements
- LayoutParserCallback {
- private final Context mContext;
- private final boolean mForMigration;
- private int mMaxItemId = -1;
- private boolean mBackupTableExists;
- private boolean mHotseatRestoreTableExists;
-
- static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
- return createDatabaseHelper(context, null, forMigration);
- }
-
- static DatabaseHelper createDatabaseHelper(Context context, String dbName,
- boolean forMigration) {
- if (dbName == null) {
- dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
- }
- DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
- // Table creation sometimes fails silently, which leads to a crash loop.
- // This way, we will try to create a table every time after crash, so the device
- // would eventually be able to recover.
- if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
- Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
- // This operation is a no-op if the table already exists.
- databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
- }
- databaseHelper.mHotseatRestoreTableExists = tableExists(
- databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
-
- databaseHelper.initIds();
- return databaseHelper;
- }
-
- /**
- * Constructor used in tests and for restore.
- */
- public DatabaseHelper(Context context, String dbName, boolean forMigration) {
- super(context, dbName, SCHEMA_VERSION);
- mContext = context;
- mForMigration = forMigration;
- }
-
- protected void initIds() {
- // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
- // the DB here
- if (mMaxItemId == -1) {
- mMaxItemId = initializeMaxItemId(getWritableDatabase());
- }
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- if (LOGD) Log.d(TAG, "creating new launcher database");
-
- mMaxItemId = 1;
-
- addFavoritesTable(db, false);
-
- // Fresh and clean launcher DB.
- mMaxItemId = initializeMaxItemId(db);
- if (!mForMigration) {
- onEmptyDbCreated();
- }
- }
-
- protected void onAddOrDeleteOp(SQLiteDatabase db) {
- if (mBackupTableExists) {
- dropTable(db, Favorites.BACKUP_TABLE_NAME);
- mBackupTableExists = false;
- }
- if (mHotseatRestoreTableExists) {
- dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
- mHotseatRestoreTableExists = false;
- }
- }
-
- /**
- * Re-composite given key in respect to database. If the current db is
- * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
- * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
- * string will be "EMPTY_DATABASE_CREATED@minimal.db".
- */
- String getKey(final String key) {
- if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
- return key;
- }
- return key + "@" + getDatabaseName();
- }
-
- /**
- * Overriden in tests.
- */
- protected void onEmptyDbCreated() {
- // Set the flag for empty DB
- LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(EMPTY_DATABASE_CREATED), true)
- .commit();
- }
-
- public long getSerialNumberForUser(UserHandle user) {
- return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
- }
-
- public long getDefaultUserSerial() {
- return getSerialNumberForUser(Process.myUserHandle());
- }
-
- private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
- Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- super.onOpen(db);
-
- File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE);
- if (!schemaFile.exists()) {
- handleOneTimeDataUpgrade(db);
- }
- DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext);
- }
-
- /**
- * One-time data updated before support of onDowngrade was added. This update is backwards
- * compatible and can safely be run multiple times.
- * Note: No new logic should be added here after release, as the new logic might not get
- * executed on an existing device.
- * TODO: Move this to db upgrade path, once the downgrade path is released.
- */
- protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
- // Remove "profile extra"
- UserCache um = UserCache.INSTANCE.get(mContext);
- for (UserHandle user : um.getUserProfiles()) {
- long serial = um.getSerialNumberForUser(user);
- String sql = "update favorites set intent = replace(intent, "
- + "';l.profile=" + serial + ";', ';') where itemType = 0;";
- db.execSQL(sql);
- }
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (LOGD) Log.d(TAG, "onUpgrade triggered: " + oldVersion);
- switch (oldVersion) {
- // The version cannot be lower that 12, as Launcher3 never supported a lower
- // version of the DB.
- case 12:
- // No-op
- case 13: {
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- // Insert new column for holding widget provider name
- db.execSQL("ALTER TABLE favorites " +
- "ADD COLUMN appWidgetProvider TEXT;");
- t.commit();
- } catch (SQLException ex) {
- Log.e(TAG, ex.getMessage(), ex);
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 14: {
- if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) {
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 15: {
- if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 16:
- // No-op
- case 17:
- // No-op
- case 18:
- // No-op
- case 19: {
- // Add userId column
- if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) {
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 20:
- if (!updateFolderItemsRank(db, true)) {
- break;
- }
- case 21:
- // No-op
- case 22: {
- if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 23:
- // No-op
- case 24:
- // No-op
- case 25:
- convertShortcutsToLauncherActivities(db);
- case 26:
- // QSB was moved to the grid. Ignore overlapping items
- case 27: {
- // Update the favorites table so that the screen ids are ordered based on
- // workspace page rank.
- IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db,
- "workspaceScreens", BaseColumns._ID, null, null, "screenRank");
- int[] original = finalScreens.toArray();
- Arrays.sort(original);
- String updatemap = "";
- for (int i = 0; i < original.length; i++) {
- if (finalScreens.get(i) != original[i]) {
- updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d",
- Favorites.SCREEN, finalScreens.get(i), original[i]);
- }
- }
- if (!TextUtils.isEmpty(updatemap)) {
- String query = String.format(Locale.ENGLISH,
- "UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d",
- Favorites.TABLE_NAME, Favorites.SCREEN, updatemap,
- Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
- db.execSQL(query);
- }
- dropTable(db, "workspaceScreens");
- }
- case 28: {
- boolean columnAdded = addIntegerColumn(
- db, Favorites.APPWIDGET_SOURCE, Favorites.CONTAINER_UNKNOWN);
- if (!columnAdded) {
- // Old version remains, which means we wipe old data
- break;
- }
- }
- case 29: {
- // Remove widget panel related leftover workspace items
- db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
- Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
- }
- case 30: {
- if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
- // Clean up first row in screen 0 as it might contain junk data.
- Log.d(TAG, "Cleaning up first row");
- db.delete(Favorites.TABLE_NAME,
- String.format(Locale.ENGLISH,
- "%1$s = %2$d AND %3$s = %4$d AND %5$s = %6$d",
- Favorites.SCREEN, 0,
- Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
- Favorites.CELLY, 0), null);
- }
- return;
- }
- case 31: {
- // DB Upgraded successfully
- return;
- }
- }
-
- // DB was not upgraded
- Log.w(TAG, "Destroying all old data.");
- createEmptyDB(db);
- }
-
- @Override
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- try {
- DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE))
- .onDowngrade(db, oldVersion, newVersion);
- } catch (Exception e) {
- Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion +
- ". Wiping databse.", e);
- createEmptyDB(db);
- }
- }
-
- /**
- * Clears all the data for a fresh start.
- */
- public void createEmptyDB(SQLiteDatabase db) {
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- dropTable(db, Favorites.TABLE_NAME);
- dropTable(db, "workspaceScreens");
- onCreate(db);
- t.commit();
- }
- }
-
- /**
- * Removes widgets which are registered to the Launcher's host, but are not present
- * in our model.
- */
- public void removeGhostWidgets(SQLiteDatabase db) {
- // Get all existing widget ids.
- final LauncherWidgetHolder holder = newLauncherWidgetHolder();
- try {
- final int[] allWidgets;
- try {
- // Although the method was defined in O, it has existed since the beginning of
- // time, so it might work on older platforms as well.
- allWidgets = holder.getAppWidgetIds();
- } catch (IncompatibleClassChangeError e) {
- Log.e(TAG, "getAppWidgetIds not supported", e);
- return;
- }
- final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
- Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
- "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
- boolean isAnyWidgetRemoved = false;
- for (int widgetId : allWidgets) {
- if (!validWidgets.contains(widgetId)) {
- try {
- FileLog.d(TAG, "Deleting invalid widget " + widgetId);
- holder.deleteAppWidgetId(widgetId);
- isAnyWidgetRemoved = true;
- } catch (RuntimeException e) {
- // Ignore
- }
- }
- }
- if (isAnyWidgetRemoved) {
- final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
- .collect(Collectors.joining(",", "[", "]"));
- final String validWidgetsIds = Arrays.stream(
- validWidgets.getArray().toArray()).mapToObj(String::valueOf)
- .collect(Collectors.joining(",", "[", "]"));
- FileLog.d(TAG, "One or more widgets was removed. db_path=" + db.getPath()
- + " allWidgetsIds=" + allWidgetsIds
- + ", validWidgetsIds=" + validWidgetsIds);
- }
- } finally {
- holder.destroy();
- }
- }
-
- /**
- * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
- * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
- */
- @Thunk void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
- try (SQLiteTransaction t = new SQLiteTransaction(db);
- // Only consider the primary user as other users can't have a shortcut.
- Cursor c = db.query(Favorites.TABLE_NAME,
- new String[] { Favorites._ID, Favorites.INTENT},
- "itemType=" + Favorites.ITEM_TYPE_SHORTCUT +
- " AND profileId=" + getDefaultUserSerial(),
- null, null, null, null);
- SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType="
- + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?")
- ) {
- final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
- final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
-
- while (c.moveToNext()) {
- String intentDescription = c.getString(intentIndex);
- Intent intent;
- try {
- intent = Intent.parseUri(intentDescription, 0);
- } catch (URISyntaxException e) {
- Log.e(TAG, "Unable to parse intent", e);
- continue;
- }
-
- if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
- continue;
- }
-
- int id = c.getInt(idIndex);
- updateStmt.bindLong(1, id);
- updateStmt.executeUpdateDelete();
- }
- t.commit();
- } catch (SQLException ex) {
- Log.w(TAG, "Error deduping shortcuts", ex);
- }
- }
-
- @Thunk boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- if (addRankColumn) {
- // Insert new column for holding rank
- db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
- }
-
- // Get a map for folder ID to folder width
- Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
- + " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
- + " GROUP BY container;",
- new String[] {Integer.toString(LauncherSettings.Favorites.ITEM_TYPE_FOLDER)});
-
- while (c.moveToNext()) {
- db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
- + "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
- new Object[] {c.getLong(1) + 1, c.getLong(0)});
- }
-
- c.close();
- t.commit();
- } catch (SQLException ex) {
- // Old version remains, which means we wipe old data
- Log.e(TAG, ex.getMessage(), ex);
- return false;
- }
- return true;
- }
-
- private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
- try (SQLiteTransaction t = new SQLiteTransaction(db)) {
- db.execSQL("ALTER TABLE favorites ADD COLUMN "
- + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
- t.commit();
- } catch (SQLException ex) {
- Log.e(TAG, ex.getMessage(), ex);
- return false;
- }
- return true;
- }
-
- // Generates a new ID to use for an object in your database. This method should be only
- // called from the main UI thread. As an exception, we do call it when we call the
- // constructor from the worker thread; however, this doesn't extend until after the
- // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
- // after that point
- @Override
- public int generateNewItemId() {
- if (mMaxItemId < 0) {
- throw new RuntimeException("Error: max item id was not initialized");
- }
- mMaxItemId += 1;
- return mMaxItemId;
- }
-
- /**
- * @return A new {@link LauncherWidgetHolder} based on the current context
- */
- @NonNull
- public LauncherWidgetHolder newLauncherWidgetHolder() {
- return LauncherWidgetHolder.newInstance(mContext);
- }
-
- @Override
- public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
- return dbInsertAndCheck(this, db, Favorites.TABLE_NAME, null, values);
- }
-
- public void checkId(ContentValues values) {
- int id = values.getAsInteger(Favorites._ID);
- mMaxItemId = Math.max(id, mMaxItemId);
- }
-
- private int initializeMaxItemId(SQLiteDatabase db) {
- return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID, Favorites.TABLE_NAME);
- }
-
- // Returns a new ID to use for an workspace screen in your database that is greater than all
- // existing screen IDs.
- private int getNewScreenId() {
- return getMaxId(getWritableDatabase(),
- "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
- Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
- Favorites.CONTAINER_DESKTOP) + 1;
- }
-
- @Thunk int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
- // TODO: Use multiple loaders with fall-back and transaction.
- int count = loader.loadLayout(db, new IntArray());
-
- // Ensure that the max ids are initialized
- mMaxItemId = initializeMaxItemId(db);
- return count;
- }
- }
-
- /**
- * @return the max _id in the provided table.
- */
- @Thunk static int getMaxId(SQLiteDatabase db, String query, Object... args) {
- int max = 0;
- try (SQLiteStatement prog = db.compileStatement(
- String.format(Locale.ENGLISH, query, args))) {
- max = (int) DatabaseUtils.longForQuery(prog, null);
- if (max < 0) {
- throw new RuntimeException("Error: could not query max id");
- }
- } catch (IllegalArgumentException exception) {
- String message = exception.getMessage();
- if (message.contains("re-open") && message.contains("already-closed")) {
- // Don't crash trying to end a transaction an an already closed DB. See b/173162852.
- } else {
- throw exception;
- }
- }
- return max;
- }
-
static class SqlArguments {
public final String table;
public final String where;
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 6e3e96c..1cd2a30 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -380,7 +380,6 @@
LauncherProvider.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";
@@ -404,18 +403,12 @@
public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
- public static final String METHOD_REFRESH_BACKUP_TABLE = "refresh_backup_table";
-
public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
- public static final String METHOD_RESTORE_BACKUP_TABLE = "restore_backup_table";
-
public static final String METHOD_UPDATE_CURRENT_OPEN_HELPER = "update_current_open_helper";
public static final String METHOD_PREP_FOR_PREVIEW = "prep_for_preview";
- public static final String METHOD_SWITCH_DATABASE = "switch_database";
-
public static final String EXTRA_VALUE = "value";
public static final String EXTRA_DB_NAME = "db_name";
diff --git a/src/com/android/launcher3/SessionCommitReceiver.java b/src/com/android/launcher3/SessionCommitReceiver.java
index 50ad2be..32c8968 100644
--- a/src/com/android/launcher3/SessionCommitReceiver.java
+++ b/src/com/android/launcher3/SessionCommitReceiver.java
@@ -71,7 +71,6 @@
}
InstallSessionHelper packageInstallerCompat = InstallSessionHelper.INSTANCE.get(context);
- packageInstallerCompat.restoreDbIfApplicable(info);
if (TextUtils.isEmpty(info.getAppPackageName())
|| info.getInstallReason() != PackageManager.INSTALL_REASON_USER
|| packageInstallerCompat.promiseIconAddedForId(info.getSessionId())) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index cbcd4d6..0b34bef 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -76,10 +76,6 @@
* Declare a new ToggleableFlag below. Give it a unique key (e.g. "QSB_ON_FIRST_SCREEN"),
* and set a default value for the flag. This will be the default value on Debug builds.
*/
- public static final BooleanFlag ENABLE_GESTURE_ERROR_DETECTION = getDebugFlag(270389990,
- "ENABLE_GESTURE_ERROR_DETECTION", ENABLED,
- "Analyze gesture events and log detected errors");
-
// When enabled the promise icon is visible in all apps while installation an app.
public static final BooleanFlag PROMISE_APPS_IN_ALL_APPS = getDebugFlag(270390012,
"PROMISE_APPS_IN_ALL_APPS", DISABLED, "Add promise icon in all-apps");
@@ -135,14 +131,6 @@
"ASSISTANT_GIVES_LAUNCHER_FOCUS", DISABLED,
"Allow Launcher to handle nav bar gestures while Assistant is running over it");
- public static final BooleanFlag ENABLE_BULK_WORKSPACE_ICON_LOADING = getDebugFlag(270392203,
- "ENABLE_BULK_WORKSPACE_ICON_LOADING", ENABLED,
- "Enable loading workspace icons in bulk.");
-
- public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(270392706,
- "ENABLE_DATABASE_RESTORE", DISABLED,
- "Enable database restore when new restore session is created");
-
public static final BooleanFlag ENABLE_OVERLAY_CONNECTION_OPTIM = getDebugFlag(270392629,
"ENABLE_OVERLAY_CONNECTION_OPTIM", DISABLED,
"Enable optimizing overlay service connection");
@@ -162,10 +150,6 @@
"SEPARATE_RECENTS_ACTIVITY", DISABLED,
"Uses a separate recents activity instead of using the integrated recents+Launcher UI");
- public static final BooleanFlag ENABLE_MINIMAL_DEVICE = getDebugFlag(270392984,
- "ENABLE_MINIMAL_DEVICE", DISABLED,
- "Allow user to toggle minimal device mode in launcher.");
-
public static final BooleanFlag ENABLE_TWO_PANEL_HOME = getDebugFlag(270392643,
"ENABLE_TWO_PANEL_HOME", ENABLED,
"Uses two panel on home screen. Only applicable on large screen devices.");
@@ -361,6 +345,11 @@
"load the current workspace screen visible to the user before the rest rather than "
+ "loading all of them at once.");
+ public static final BooleanFlag CHANGE_MODEL_DELEGATE_LOADING_ORDER = getDebugFlag(251502424,
+ "CHANGE_MODEL_DELEGATE_LOADING_ORDER", DISABLED,
+ "changes the timing of the loading and binding of delegate items during "
+ + "data preparation for loading the home screen");
+
public static final BooleanFlag ENABLE_GRID_ONLY_OVERVIEW = getDebugFlag(270397206,
"ENABLE_GRID_ONLY_OVERVIEW", DISABLED,
"Enable a grid-only overview without a focused task.");
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 0767e69..372e9bf 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -229,8 +229,8 @@
query += " or " + LauncherSettings.Favorites.SCREEN + " = "
+ Workspace.SECOND_SCREEN_ID;
}
- loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI,
- query);
+ loadWorkspaceForPreviewSurfaceRenderer(new ArrayList<>(),
+ LauncherSettings.Favorites.PREVIEW_CONTENT_URI, query);
final SparseArray<Size> spanInfo =
getLoadedLauncherWidgetInfo(previewContext.getBaseContext());
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 91ace27..358992e 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -63,7 +63,7 @@
protected final BgDataModel mBgDataModel;
private final AllAppsList mBgAllAppsList;
- private final Callbacks[] mCallbacksList;
+ final Callbacks[] mCallbacksList;
private int mMyBindingId;
@@ -293,8 +293,10 @@
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mUiExecutor);
bindAppWidgets(currentAppWidgets, mUiExecutor);
- mExtraItems.forEach(item ->
- executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+ if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mExtraItems.forEach(item ->
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+ }
RunnableList pendingTasks = new RunnableList();
Executor pendingExecutor = pendingTasks::add;
@@ -382,14 +384,22 @@
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems;
ArrayList<LauncherAppWidgetInfo> appWidgets;
+ ArrayList<FixedContainerItems> fciList = new ArrayList<>();
synchronized (mBgDataModel) {
workspaceItems = new ArrayList<>(mBgDataModel.workspaceItems);
appWidgets = new ArrayList<>(mBgDataModel.appWidgets);
+ if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mBgDataModel.extraItems.forEach(fciList::add);
+ }
}
workspaceItems.forEach(it -> mBoundItemIds.add(it.id));
appWidgets.forEach(it -> mBoundItemIds.add(it.id));
+ if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ fciList.forEach(item ->
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mUiExecutor));
+ }
sortWorkspaceItemsSpatially(mApp.getInvariantDeviceProfile(), workspaceItems);
diff --git a/src/com/android/launcher3/model/DatabaseHelper.java b/src/com/android/launcher3/model/DatabaseHelper.java
new file mode 100644
index 0000000..3578b67
--- /dev/null
+++ b/src/com/android/launcher3/model/DatabaseHelper.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (C) 2023 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.model;
+
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.SQLException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.os.Process;
+import android.os.UserHandle;
+import android.provider.BaseColumns;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherFiles;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.provider.LauncherDbUtils;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.NoLocaleSQLiteHelper;
+import com.android.launcher3.util.PackageManagerHelper;
+import com.android.launcher3.util.Thunk;
+import com.android.launcher3.widget.LauncherWidgetHolder;
+
+import java.io.File;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * SqLite database for launcher home-screen model
+ * The class is subclassed in tests to create an in-memory db.
+ */
+public class DatabaseHelper extends NoLocaleSQLiteHelper implements
+ LayoutParserCallback {
+
+ /**
+ * Represents the schema of the database. Changes in scheme need not be backwards compatible.
+ * When increasing the scheme version, ensure that downgrade_schema.json is updated
+ */
+ public static final int SCHEMA_VERSION = 31;
+ private static final String TAG = "DatabaseHelper";
+ private static final boolean LOGD = false;
+
+ private static final String DOWNGRADE_SCHEMA_FILE = "downgrade_schema.json";
+
+ private final Context mContext;
+ private final boolean mForMigration;
+ private int mMaxItemId = -1;
+ public boolean mHotseatRestoreTableExists;
+
+ public static DatabaseHelper createDatabaseHelper(Context context, boolean forMigration) {
+ return createDatabaseHelper(context, null, forMigration);
+ }
+
+ public static DatabaseHelper createDatabaseHelper(Context context, String dbName,
+ boolean forMigration) {
+ if (dbName == null) {
+ dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
+ }
+ DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
+ // Table creation sometimes fails silently, which leads to a crash loop.
+ // This way, we will try to create a table every time after crash, so the device
+ // would eventually be able to recover.
+ if (!tableExists(databaseHelper.getReadableDatabase(), Favorites.TABLE_NAME)) {
+ Log.e(TAG, "Tables are missing after onCreate has been called. Trying to recreate");
+ // This operation is a no-op if the table already exists.
+ databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
+ }
+ databaseHelper.mHotseatRestoreTableExists = tableExists(
+ databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+
+ databaseHelper.initIds();
+ return databaseHelper;
+ }
+
+ /**
+ * Constructor used in tests and for restore.
+ */
+ public DatabaseHelper(Context context, String dbName, boolean forMigration) {
+ super(context, dbName, SCHEMA_VERSION);
+ mContext = context;
+ mForMigration = forMigration;
+ }
+
+ protected void initIds() {
+ // In the case where neither onCreate nor onUpgrade gets called, we read the maxId from
+ // the DB here
+ if (mMaxItemId == -1) {
+ mMaxItemId = initializeMaxItemId(getWritableDatabase());
+ }
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ if (LOGD) Log.d(TAG, "creating new launcher database");
+
+ mMaxItemId = 1;
+
+ addFavoritesTable(db, false);
+
+ // Fresh and clean launcher DB.
+ mMaxItemId = initializeMaxItemId(db);
+ if (!mForMigration) {
+ onEmptyDbCreated();
+ }
+ }
+
+ public void onAddOrDeleteOp(SQLiteDatabase db) {
+ if (mHotseatRestoreTableExists) {
+ dropTable(db, Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
+ mHotseatRestoreTableExists = false;
+ }
+ }
+
+ /**
+ * Re-composite given key in respect to database. If the current db is
+ * {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
+ * given key. e.g. consider key="EMPTY_DATABASE_CREATED", dbName="minimal.db", the returning
+ * string will be "EMPTY_DATABASE_CREATED@minimal.db".
+ */
+ public String getKey(final String key) {
+ if (TextUtils.equals(getDatabaseName(), LauncherFiles.LAUNCHER_DB)) {
+ return key;
+ }
+ return key + "@" + getDatabaseName();
+ }
+
+ /**
+ * Overridden in tests.
+ */
+ protected void onEmptyDbCreated() {
+ // Set the flag for empty DB
+ LauncherPrefs.getPrefs(mContext).edit().putBoolean(getKey(
+ LauncherProvider.EMPTY_DATABASE_CREATED), true)
+ .commit();
+ }
+
+ public long getSerialNumberForUser(UserHandle user) {
+ return UserCache.INSTANCE.get(mContext).getSerialNumberForUser(user);
+ }
+
+ public long getDefaultUserSerial() {
+ return getSerialNumberForUser(Process.myUserHandle());
+ }
+
+ private void addFavoritesTable(SQLiteDatabase db, boolean optional) {
+ Favorites.addTableToDb(db, getDefaultUserSerial(), optional);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ super.onOpen(db);
+
+ File schemaFile = mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE);
+ if (!schemaFile.exists()) {
+ handleOneTimeDataUpgrade(db);
+ }
+ DbDowngradeHelper.updateSchemaFile(schemaFile, SCHEMA_VERSION, mContext);
+ }
+
+ /**
+ * One-time data updated before support of onDowngrade was added. This update is backwards
+ * compatible and can safely be run multiple times.
+ * Note: No new logic should be added here after release, as the new logic might not get
+ * executed on an existing device.
+ * TODO: Move this to db upgrade path, once the downgrade path is released.
+ */
+ protected void handleOneTimeDataUpgrade(SQLiteDatabase db) {
+ // Remove "profile extra"
+ UserCache um = UserCache.INSTANCE.get(mContext);
+ for (UserHandle user : um.getUserProfiles()) {
+ long serial = um.getSerialNumberForUser(user);
+ String sql = "update favorites set intent = replace(intent, "
+ + "';l.profile=" + serial + ";', ';') where itemType = 0;";
+ db.execSQL(sql);
+ }
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (LOGD) {
+ Log.d(TAG, "onUpgrade triggered: " + oldVersion);
+ }
+ switch (oldVersion) {
+ // The version cannot be lower that 12, as Launcher3 never supported a lower
+ // version of the DB.
+ case 12:
+ // No-op
+ case 13: {
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ // Insert new column for holding widget provider name
+ db.execSQL("ALTER TABLE favorites ADD COLUMN appWidgetProvider TEXT;");
+ t.commit();
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 14: {
+ if (!addIntegerColumn(db, Favorites.MODIFIED, 0)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 15: {
+ if (!addIntegerColumn(db, Favorites.RESTORED, 0)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 16:
+ // No-op
+ case 17:
+ // No-op
+ case 18:
+ // No-op
+ case 19: {
+ // Add userId column
+ if (!addIntegerColumn(db, Favorites.PROFILE_ID, getDefaultUserSerial())) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 20:
+ if (!updateFolderItemsRank(db, true)) {
+ break;
+ }
+ case 21:
+ // No-op
+ case 22: {
+ if (!addIntegerColumn(db, Favorites.OPTIONS, 0)) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 23:
+ // No-op
+ case 24:
+ // No-op
+ case 25:
+ convertShortcutsToLauncherActivities(db);
+ case 26:
+ // QSB was moved to the grid. Ignore overlapping items
+ case 27: {
+ // Update the favorites table so that the screen ids are ordered based on
+ // workspace page rank.
+ IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db,
+ "workspaceScreens", BaseColumns._ID, null, null, "screenRank");
+ int[] original = finalScreens.toArray();
+ Arrays.sort(original);
+ String updatemap = "";
+ for (int i = 0; i < original.length; i++) {
+ if (finalScreens.get(i) != original[i]) {
+ updatemap += String.format(Locale.ENGLISH, " WHEN %1$s=%2$d THEN %3$d",
+ Favorites.SCREEN, finalScreens.get(i), original[i]);
+ }
+ }
+ if (!TextUtils.isEmpty(updatemap)) {
+ String query = String.format(Locale.ENGLISH,
+ "UPDATE %1$s SET %2$s=CASE %3$s ELSE %2$s END WHERE %4$s = %5$d",
+ Favorites.TABLE_NAME, Favorites.SCREEN, updatemap,
+ Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
+ db.execSQL(query);
+ }
+ dropTable(db, "workspaceScreens");
+ }
+ case 28: {
+ boolean columnAdded = addIntegerColumn(
+ db, Favorites.APPWIDGET_SOURCE, Favorites.CONTAINER_UNKNOWN);
+ if (!columnAdded) {
+ // Old version remains, which means we wipe old data
+ break;
+ }
+ }
+ case 29: {
+ // Remove widget panel related leftover workspace items
+ db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
+ Favorites.SCREEN, IntArray.wrap(-777, -778)), null);
+ }
+ case 30: {
+ if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+ // Clean up first row in screen 0 as it might contain junk data.
+ Log.d(TAG, "Cleaning up first row");
+ db.delete(Favorites.TABLE_NAME,
+ String.format(Locale.ENGLISH,
+ "%1$s = %2$d AND %3$s = %4$d AND %5$s = %6$d",
+ Favorites.SCREEN, 0,
+ Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP,
+ Favorites.CELLY, 0), null);
+ }
+ return;
+ }
+ case 31: {
+ // DB Upgraded successfully
+ return;
+ }
+ }
+
+ // DB was not upgraded
+ Log.w(TAG, "Destroying all old data.");
+ createEmptyDB(db);
+ }
+
+ @Override
+ public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ try {
+ DbDowngradeHelper.parse(mContext.getFileStreamPath(DOWNGRADE_SCHEMA_FILE))
+ .onDowngrade(db, oldVersion, newVersion);
+ } catch (Exception e) {
+ Log.d(TAG, "Unable to downgrade from: " + oldVersion + " to " + newVersion
+ + ". Wiping database.", e);
+ createEmptyDB(db);
+ }
+ }
+
+ /**
+ * Clears all the data for a fresh start.
+ */
+ public void createEmptyDB(SQLiteDatabase db) {
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ dropTable(db, Favorites.TABLE_NAME);
+ dropTable(db, "workspaceScreens");
+ onCreate(db);
+ t.commit();
+ }
+ }
+
+ /**
+ * Removes widgets which are registered to the Launcher's host, but are not present
+ * in our model.
+ */
+ public void removeGhostWidgets(SQLiteDatabase db) {
+ // Get all existing widget ids.
+ final LauncherWidgetHolder holder = newLauncherWidgetHolder();
+ try {
+ final int[] allWidgets;
+ try {
+ // Although the method was defined in O, it has existed since the beginning of
+ // time, so it might work on older platforms as well.
+ allWidgets = holder.getAppWidgetIds();
+ } catch (IncompatibleClassChangeError e) {
+ Log.e(TAG, "getAppWidgetIds not supported", e);
+ return;
+ }
+ final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
+ Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
+ "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
+ boolean isAnyWidgetRemoved = false;
+ for (int widgetId : allWidgets) {
+ if (!validWidgets.contains(widgetId)) {
+ try {
+ FileLog.d(TAG, "Deleting invalid widget " + widgetId);
+ holder.deleteAppWidgetId(widgetId);
+ isAnyWidgetRemoved = true;
+ } catch (RuntimeException e) {
+ // Ignore
+ }
+ }
+ }
+ if (isAnyWidgetRemoved) {
+ final String allWidgetsIds = Arrays.stream(allWidgets).mapToObj(String::valueOf)
+ .collect(Collectors.joining(",", "[", "]"));
+ final String validWidgetsIds = Arrays.stream(
+ validWidgets.getArray().toArray()).mapToObj(String::valueOf)
+ .collect(Collectors.joining(",", "[", "]"));
+ FileLog.d(TAG,
+ "One or more widgets was removed. db_path=" + db.getPath()
+ + " allWidgetsIds=" + allWidgetsIds
+ + ", validWidgetsIds=" + validWidgetsIds);
+ }
+ } finally {
+ holder.destroy();
+ }
+ }
+
+ /**
+ * Replaces all shortcuts of type {@link Favorites#ITEM_TYPE_SHORTCUT} which have a valid
+ * launcher activity target with {@link Favorites#ITEM_TYPE_APPLICATION}.
+ */
+ @Thunk
+ void convertShortcutsToLauncherActivities(SQLiteDatabase db) {
+ try (SQLiteTransaction t = new SQLiteTransaction(db);
+ // Only consider the primary user as other users can't have a shortcut.
+ Cursor c = db.query(Favorites.TABLE_NAME,
+ new String[]{Favorites._ID, Favorites.INTENT},
+ "itemType=" + Favorites.ITEM_TYPE_SHORTCUT
+ + " AND profileId=" + getDefaultUserSerial(),
+ null, null, null, null);
+ SQLiteStatement updateStmt = db.compileStatement("UPDATE favorites SET itemType="
+ + Favorites.ITEM_TYPE_APPLICATION + " WHERE _id=?")
+ ) {
+ final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
+ final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
+
+ while (c.moveToNext()) {
+ String intentDescription = c.getString(intentIndex);
+ Intent intent;
+ try {
+ intent = Intent.parseUri(intentDescription, 0);
+ } catch (URISyntaxException e) {
+ Log.e(TAG, "Unable to parse intent", e);
+ continue;
+ }
+
+ if (!PackageManagerHelper.isLauncherAppTarget(intent)) {
+ continue;
+ }
+
+ int id = c.getInt(idIndex);
+ updateStmt.bindLong(1, id);
+ updateStmt.executeUpdateDelete();
+ }
+ t.commit();
+ } catch (SQLException ex) {
+ Log.w(TAG, "Error deduping shortcuts", ex);
+ }
+ }
+
+ @Thunk
+ boolean updateFolderItemsRank(SQLiteDatabase db, boolean addRankColumn) {
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ if (addRankColumn) {
+ // Insert new column for holding rank
+ db.execSQL("ALTER TABLE favorites ADD COLUMN rank INTEGER NOT NULL DEFAULT 0;");
+ }
+
+ // Get a map for folder ID to folder width
+ Cursor c = db.rawQuery("SELECT container, MAX(cellX) FROM favorites"
+ + " WHERE container IN (SELECT _id FROM favorites WHERE itemType = ?)"
+ + " GROUP BY container;",
+ new String[]{Integer.toString(Favorites.ITEM_TYPE_FOLDER)});
+
+ while (c.moveToNext()) {
+ db.execSQL("UPDATE favorites SET rank=cellX+(cellY*?) WHERE "
+ + "container=? AND cellX IS NOT NULL AND cellY IS NOT NULL;",
+ new Object[]{c.getLong(1) + 1, c.getLong(0)});
+ }
+
+ c.close();
+ t.commit();
+ } catch (SQLException ex) {
+ // Old version remains, which means we wipe old data
+ Log.e(TAG, ex.getMessage(), ex);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean addIntegerColumn(SQLiteDatabase db, String columnName, long defaultValue) {
+ try (SQLiteTransaction t = new SQLiteTransaction(db)) {
+ db.execSQL("ALTER TABLE favorites ADD COLUMN "
+ + columnName + " INTEGER NOT NULL DEFAULT " + defaultValue + ";");
+ t.commit();
+ } catch (SQLException ex) {
+ Log.e(TAG, ex.getMessage(), ex);
+ return false;
+ }
+ return true;
+ }
+
+ // Generates a new ID to use for an object in your database. This method should be only
+ // called from the main UI thread. As an exception, we do call it when we call the
+ // constructor from the worker thread; however, this doesn't extend until after the
+ // constructor is called, and we only pass a reference to LauncherProvider to LauncherApp
+ // after that point
+ @Override
+ public int generateNewItemId() {
+ if (mMaxItemId < 0) {
+ throw new RuntimeException("Error: max item id was not initialized");
+ }
+ mMaxItemId += 1;
+ return mMaxItemId;
+ }
+
+ /**
+ * @return A new {@link LauncherWidgetHolder} based on the current context
+ */
+ @NonNull
+ public LauncherWidgetHolder newLauncherWidgetHolder() {
+ return LauncherWidgetHolder.newInstance(mContext);
+ }
+
+ @Override
+ public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
+ return dbInsertAndCheck(db, Favorites.TABLE_NAME, values);
+ }
+
+ public int dbInsertAndCheck(SQLiteDatabase db, String table, ContentValues values) {
+ if (values == null) {
+ throw new RuntimeException("Error: attempting to insert null values");
+ }
+ if (!values.containsKey(LauncherSettings.Favorites._ID)) {
+ throw new RuntimeException("Error: attempting to add item without specifying an id");
+ }
+ checkId(values);
+ return (int) db.insert(table, null, values);
+ }
+
+ public void checkId(ContentValues values) {
+ int id = values.getAsInteger(Favorites._ID);
+ mMaxItemId = Math.max(id, mMaxItemId);
+ }
+
+ private int initializeMaxItemId(SQLiteDatabase db) {
+ return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s", Favorites._ID,
+ Favorites.TABLE_NAME);
+ }
+
+ /**
+ * Returns a new ID to use for a workspace screen in your database that is greater than all
+ * existing screen IDs
+ */
+ public int getNewScreenId() {
+ return getMaxId(getWritableDatabase(),
+ "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
+ Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
+ Favorites.CONTAINER_DESKTOP) + 1;
+ }
+
+ public int loadFavorites(SQLiteDatabase db, AutoInstallsLayout loader) {
+ // TODO: Use multiple loaders with fall-back and transaction.
+ int count = loader.loadLayout(db, new IntArray());
+
+ // Ensure that the max ids are initialized
+ mMaxItemId = initializeMaxItemId(db);
+ return count;
+ }
+
+ /**
+ * @return the max _id in the provided table.
+ */
+ private static int getMaxId(SQLiteDatabase db, String query, Object... args) {
+ int max = 0;
+ try (SQLiteStatement prog = db.compileStatement(
+ String.format(Locale.ENGLISH, query, args))) {
+ max = (int) DatabaseUtils.longForQuery(prog, null);
+ if (max < 0) {
+ throw new RuntimeException("Error: could not query max id");
+ }
+ } catch (IllegalArgumentException exception) {
+ String message = exception.getMessage();
+ if (message.contains("re-open") && message.contains("already-closed")) {
+ // Don't crash trying to end a transaction an an already closed DB. See b/173162852.
+ } else {
+ throw exception;
+ }
+ }
+ return max;
+ }
+}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 3e99772..481cc6e 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -138,6 +138,7 @@
private final UserManagerState mUserManagerState = new UserManagerState();
protected final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap = new ArrayMap<>();
+ private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
private boolean mStopped;
@@ -211,6 +212,14 @@
}
logASplit(timingLogger, "loadWorkspace");
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ verifyNotStopped();
+ mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+ mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+ mModelDelegate.markActive();
+ logASplit(timingLogger, "workspaceDelegateItems");
+ }
+
// Sanitize data re-syncs widgets/shortcuts based on the workspace loaded from db.
// sanitizeData should not be invoked if the workspace is loaded from a db different
// from the main db as defined in the invariant device profile.
@@ -246,6 +255,11 @@
}
logASplit(timingLogger, "loadAllApps");
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
+ mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+ logASplit(timingLogger, "allAppsDelegateItems");
+ }
verifyNotStopped();
mLauncherBinder.bindAllApps();
logASplit(timingLogger, "bindAllApps");
@@ -296,6 +310,12 @@
logASplit(timingLogger, "bindWidgets");
verifyNotStopped();
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
+ logASplit(timingLogger, "otherDelegateItems");
+ verifyNotStopped();
+ }
+
updateHandler.updateIcons(allWidgetsList,
new ComponentWithIconCachingLogic(mApp.getContext(), true),
mApp.getModel()::onWidgetLabelsUpdated);
@@ -334,9 +354,14 @@
null /* selection */, memoryLogger);
}
- protected void loadWorkspace(
+ protected void loadWorkspaceForPreviewSurfaceRenderer(
List<ShortcutInfo> allDeepShortcuts, Uri contentUri, String selection) {
loadWorkspace(allDeepShortcuts, contentUri, selection, null);
+ if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+ mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+ mModelDelegate.markActive();
+ }
}
protected void loadWorkspace(
@@ -376,7 +401,7 @@
final PackageUserKey tempPackageKey = new PackageUserKey(null, null);
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
- Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts = new HashMap<>();
+ mShortcutKeyToPinnedShortcuts = new HashMap<>();
final LoaderCursor c = new LoaderCursor(
contentResolver.query(contentUri, null, selection, null, null), contentUri,
mApp, mUserManagerState);
@@ -397,7 +422,7 @@
.query(ShortcutRequest.PINNED);
if (pinnedShortcuts.wasSuccess()) {
for (ShortcutInfo shortcut : pinnedShortcuts) {
- shortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
+ mShortcutKeyToPinnedShortcuts.put(ShortcutKey.fromInfo(shortcut),
shortcut);
}
} else {
@@ -414,22 +439,22 @@
while (!mStopped && c.moveToNext()) {
processWorkspaceItem(c, memoryLogger, installingPkgs, isSdCardReady,
- tempPackageKey, widgetHelper, pmHelper, shortcutKeyToPinnedShortcuts,
+ tempPackageKey, widgetHelper, pmHelper,
iconRequestInfos, unlockedUsers, isSafeMode, allDeepShortcuts);
}
- maybeLoadWorkspaceIconsInBulk(iconRequestInfos);
+ tryLoadWorkspaceIconsInBulk(iconRequestInfos);
} finally {
IOUtils.closeSilently(c);
}
- // Load delegate items
- mModelDelegate.loadHotseatItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
- mModelDelegate.loadAllAppsItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
- mModelDelegate.loadWidgetsRecommendationItems();
- mModelDelegate.markActive();
-
- // Load string cache
- mModelDelegate.loadStringCache(mBgDataModel.stringCache);
+ if (!FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
+ mModelDelegate.loadAndBindWorkspaceItems(mUserManagerState,
+ mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+ mModelDelegate.loadAndBindAllAppsItems(mUserManagerState,
+ mLauncherBinder.mCallbacksList, mShortcutKeyToPinnedShortcuts);
+ mModelDelegate.loadAndBindOtherItems(mLauncherBinder.mCallbacksList);
+ mModelDelegate.markActive();
+ }
// Break early if we've stopped loading
if (mStopped) {
@@ -474,7 +499,6 @@
PackageUserKey tempPackageKey,
WidgetManagerHelper widgetHelper,
PackageManagerHelper pmHelper,
- Map<ShortcutKey, ShortcutInfo> shortcutKeyToPinnedShortcuts,
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos,
LongSparseArray<Boolean> unlockedUsers,
boolean isSafeMode,
@@ -598,12 +622,12 @@
// Already verified above that user is same as default user
info = c.getRestoredItemInfo(intent);
} else if (c.itemType == Favorites.ITEM_TYPE_APPLICATION) {
- info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon,
- !FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get());
+ info = c.getAppShortcutInfo(
+ intent, allowMissingTarget, useLowResIcon, false);
} else if (c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
ShortcutKey key = ShortcutKey.fromIntent(intent, c.user);
if (unlockedUsers.get(c.serialNumber)) {
- ShortcutInfo pinnedShortcut = shortcutKeyToPinnedShortcuts.get(key);
+ ShortcutInfo pinnedShortcut = mShortcutKeyToPinnedShortcuts.get(key);
if (pinnedShortcut == null) {
// The shortcut is no longer valid.
c.markDeleted("Pinned shortcut not found");
@@ -861,21 +885,19 @@
}
}
- private void maybeLoadWorkspaceIconsInBulk(
+ private void tryLoadWorkspaceIconsInBulk(
List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos) {
- if (FeatureFlags.ENABLE_BULK_WORKSPACE_ICON_LOADING.get()) {
- Trace.beginSection("LoadWorkspaceIconsInBulk");
- try {
- mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
- for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) {
- WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
- if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
- iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
- }
+ Trace.beginSection("LoadWorkspaceIconsInBulk");
+ try {
+ mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
+ for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) {
+ WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
+ if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
+ iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
}
- } finally {
- Trace.endSection();
}
+ } finally {
+ Trace.endSection();
}
}
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 0639a6c..7e7bfb3 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.content.pm.ShortcutInfo;
+import androidx.annotation.NonNull;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
@@ -68,9 +69,7 @@
this.mContext = context;
}
- /**
- * Called periodically to validate and update any data
- */
+ /** Called periodically to validate and update any data */
@WorkerThread
public void validateData() {
if (hasShortcutsPermission(mApp.getContext())
@@ -79,36 +78,32 @@
}
}
- /**
- * Load hot seat items if any in the data model
- */
+ /** Load workspace items (for example, those in the hot seat) if any in the data model */
@WorkerThread
- public void loadHotseatItems(UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
+ public void loadAndBindWorkspaceItems(@NonNull UserManagerState ums,
+ @NonNull BgDataModel.Callbacks[] callbacks,
+ @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
- /**
- * Load all apps items if any in the data model
- */
+ /** Load all apps items if any in the data model */
@WorkerThread
- public void loadAllAppsItems(UserManagerState ums,
- Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
+ public void loadAndBindAllAppsItems(@NonNull UserManagerState ums,
+ @NonNull BgDataModel.Callbacks[] callbacks,
+ @NonNull Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
- /**
- * Load widget recommendation items if any in the data model
- */
+ /** Load other items like widget recommendations if any in the data model */
@WorkerThread
- public void loadWidgetsRecommendationItems() { }
+ public void loadAndBindOtherItems(@NonNull BgDataModel.Callbacks[] callbacks) { }
- /**
- * Marks the ModelDelegate as active
- */
+ /** binds everything not bound by launcherBinder */
+ @WorkerThread
+ public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) { }
+
+ /** Marks the ModelDelegate as active */
public void markActive() { }
- /**
- * Load String cache
- */
+ /** Load String cache */
@WorkerThread
- public void loadStringCache(StringCache cache) {
+ public void loadStringCache(@NonNull StringCache cache) {
cache.loadStrings(mContext);
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 125b4ce..47bfe85 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -29,14 +29,11 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -204,26 +201,6 @@
return list;
}
- /**
- * Attempt to restore workspace layout if the session is triggered due to device restore.
- */
- public boolean restoreDbIfApplicable(@NonNull final SessionInfo info) {
- if (!FeatureFlags.ENABLE_DATABASE_RESTORE.get()) {
- return false;
- }
- if (isRestore(info)) {
- LauncherSettings.Settings.call(mAppContext.getContentResolver(),
- LauncherSettings.Settings.METHOD_RESTORE_BACKUP_TABLE);
- return true;
- }
- return false;
- }
-
- @RequiresApi(26)
- private static boolean isRestore(@NonNull final SessionInfo info) {
- return info.getInstallReason() == PackageManager.INSTALL_REASON_DEVICE_RESTORE;
- }
-
@WorkerThread
public boolean promiseIconAddedForId(final int sessionId) {
return getPromiseIconIds().contains(sessionId);
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 6450c8e..c4eb14f 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -44,10 +44,10 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
-import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.model.GridBackupTable;
import com.android.launcher3.model.data.AppInfo;
diff --git a/src/com/android/launcher3/qsb/QsbContainerView.java b/src/com/android/launcher3/qsb/QsbContainerView.java
index f295204..1e3be27 100644
--- a/src/com/android/launcher3/qsb/QsbContainerView.java
+++ b/src/com/android/launcher3/qsb/QsbContainerView.java
@@ -40,6 +40,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
@@ -65,6 +66,7 @@
* @param context
* @return String
*/
+ @WorkerThread
@Nullable
public static String getSearchWidgetPackageName(@NonNull Context context) {
String providerPkg = Settings.Global.getString(context.getContentResolver(),
@@ -84,6 +86,7 @@
* @param context
* @return AppWidgetProviderInfo
*/
+ @WorkerThread
@Nullable
public static AppWidgetProviderInfo getSearchWidgetProviderInfo(@NonNull Context context) {
String providerPkg = getSearchWidgetPackageName(context);
@@ -110,6 +113,7 @@
/**
* returns componentName for searchWidget if package name is known.
*/
+ @WorkerThread
@Nullable
public static ComponentName getSearchComponentName(@NonNull Context context) {
AppWidgetProviderInfo providerInfo =
@@ -317,6 +321,7 @@
* If widgetCategory is not supported, or no such widget is found, returns the first widget
* provided by the package.
*/
+ @WorkerThread
protected AppWidgetProviderInfo getSearchWidgetProvider() {
return getSearchWidgetProviderInfo(getContext());
}
diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
index d849c8f..8a092bf 100644
--- a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
+++ b/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
@@ -37,8 +37,6 @@
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.R;
@@ -74,7 +72,7 @@
mSchemaFile.delete();
assertFalse(mSchemaFile.exists());
DbDowngradeHelper.updateSchemaFile(mSchemaFile, 0, mContext);
- assertEquals(LauncherProvider.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
+ assertEquals(DatabaseHelper.SCHEMA_VERSION, DbDowngradeHelper.parse(mSchemaFile).version);
}
@Test
@@ -140,7 +138,7 @@
@Override
public void onOpen(SQLiteDatabase db) { }
};
- assertEquals(LauncherProvider.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
+ assertEquals(DatabaseHelper.SCHEMA_VERSION, helper.getWritableDatabase().getVersion());
try (Cursor c = helper.getWritableDatabase().query(Favorites.TABLE_NAME,
null, null, null, null, null, null)) {
@@ -165,7 +163,7 @@
mSchemaFile.delete();
mDbFile.delete();
- DbDowngradeHelper.updateSchemaFile(mSchemaFile, LauncherProvider.SCHEMA_VERSION, mContext);
+ DbDowngradeHelper.updateSchemaFile(mSchemaFile, DatabaseHelper.SCHEMA_VERSION, mContext);
DatabaseHelper dbHelper = new DatabaseHelper(mContext, DB_FILE, false) {
@Override
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 9c8de1c..aa091b6 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -26,8 +26,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.launcher3.LauncherProvider.DatabaseHelper;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.DatabaseHelper;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 4f6cc64..cefba16 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -106,6 +106,8 @@
private static boolean sDumpWasGenerated = false;
private static boolean sActivityLeakReported = false;
+ private static boolean sSeenKeygard = false;
+
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
@@ -237,9 +239,13 @@
@Before
public void setUp() throws Exception {
mLauncher.onTestStart();
- Assert.assertTrue("Keyguard is visible, which is likely caused by a crash in SysUI",
- TestHelpers.wait(
- Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000));
+
+ sSeenKeygard = sSeenKeygard
+ || !TestHelpers.wait(
+ Until.gone(By.res(SYSTEMUI_PACKAGE, "keyguard_status_view")), 60000);
+
+ Assert.assertFalse("Keyguard is visible, which is likely caused by a crash in SysUI",
+ sSeenKeygard);
final String launcherPackageName = mDevice.getLauncherPackageName();
try {
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 545b645..fdfeb7d 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -61,6 +61,7 @@
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.DatabaseHelper;
import com.android.launcher3.model.ItemInstallQueue;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;