Using persisted item storage for install queue.
Shared prefs are loaded during startup and should avoid large objects
Change-Id: Ibb5c8307dbccb9414b42454825e6c3c2a972efa6
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 7e45021..cf32514 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -297,8 +297,7 @@
.filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
.map(ShortcutKey::fromItemInfo),
// Pending shortcuts
- ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts()
- .stream().filter(si -> si.user.equals(user)))
+ ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
.collect(groupingBy(ShortcutKey::getPackageName,
mapping(ShortcutKey::getId, Collectors.toSet())));
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index 6238db3..5e48a0f 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -21,7 +21,6 @@
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID;
import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -30,11 +29,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.ShortcutInfo;
-import android.os.Process;
import android.os.UserHandle;
import android.util.Log;
import android.util.Pair;
@@ -47,27 +44,19 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.PersistedItemArray;
import com.android.launcher3.util.Preconditions;
-import org.json.JSONException;
-import org.json.JSONObject;
-import org.json.JSONStringer;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
-import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* Class to maintain a queue of pending items to be added to the workspace.
@@ -79,7 +68,6 @@
public static final int FLAG_DRAG_AND_DROP = 4;
private static final String TAG = "InstallShortcutReceiver";
- private static final boolean DBG = false;
// The set of shortcuts that are pending install
private static final String APPS_PENDING_INSTALL = "apps_to_install";
@@ -90,24 +78,34 @@
public static MainThreadInitializedObject<ItemInstallQueue> INSTANCE =
new MainThreadInitializedObject<>(ItemInstallQueue::new);
+ private final PersistedItemArray<PendingInstallShortcutInfo> mStorage =
+ new PersistedItemArray<>(APPS_PENDING_INSTALL);
private final Context mContext;
// Determines whether to defer installing shortcuts immediately until
// processAllPendingInstalls() is called.
private int mInstallQueueDisabledFlags = 0;
+ // Only accessed on worker thread
+ private List<PendingInstallShortcutInfo> mItems;
+
private ItemInstallQueue(Context context) {
mContext = context;
}
@WorkerThread
+ private void ensureQueueLoaded() {
+ Preconditions.assertWorkerThread();
+ if (mItems == null) {
+ mItems = mStorage.read(mContext, this::decode);
+ }
+ }
+
+ @WorkerThread
private void addToQueue(PendingInstallShortcutInfo info) {
- String encoded = info.encodeToString(mContext);
- SharedPreferences prefs = Utilities.getPrefs(mContext);
- Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
- strings = (strings != null) ? new HashSet<>(strings) : new HashSet<>(1);
- strings.add(encoded);
- prefs.edit().putStringSet(APPS_PENDING_INSTALL, strings).apply();
+ ensureQueueLoaded();
+ mItems.add(info);
+ mStorage.write(mContext, mItems);
}
@WorkerThread
@@ -117,28 +115,21 @@
// Launcher not loaded
return;
}
-
- ArrayList<Pair<ItemInfo, Object>> installQueue = new ArrayList<>();
- SharedPreferences prefs = Utilities.getPrefs(mContext);
- Set<String> strings = prefs.getStringSet(APPS_PENDING_INSTALL, null);
- if (DBG) Log.d(TAG, "Getting and clearing APPS_PENDING_INSTALL: " + strings);
- if (strings == null) {
+ ensureQueueLoaded();
+ if (mItems.isEmpty()) {
return;
}
- for (String encoded : strings) {
- PendingInstallShortcutInfo info = decode(encoded, mContext);
- if (info == null) {
- continue;
- }
+ List<Pair<ItemInfo, Object>> installQueue = mItems.stream()
+ .map(info -> info.getItemInfo(mContext))
+ .collect(Collectors.toList());
- // Generate a shortcut info to add into the model
- installQueue.add(info.getItemInfo(mContext));
- }
- prefs.edit().remove(APPS_PENDING_INSTALL).apply();
+ // Add the items and clear queue
if (!installQueue.isEmpty()) {
launcher.getModel().addAndBindAddedWorkspaceItems(installQueue);
}
+ mItems.clear();
+ mStorage.getFile(mContext).delete();
}
/**
@@ -149,33 +140,11 @@
if (packageNames.isEmpty()) {
return;
}
- Preconditions.assertWorkerThread();
-
- SharedPreferences sp = Utilities.getPrefs(mContext);
- Set<String> strings = sp.getStringSet(APPS_PENDING_INSTALL, null);
- if (DBG) {
- Log.d(TAG, "APPS_PENDING_INSTALL: " + strings
- + ", removing packages: " + packageNames);
+ ensureQueueLoaded();
+ if (mItems.removeIf(item ->
+ item.user.equals(user) && packageNames.contains(getIntentPackage(item.intent)))) {
+ mStorage.write(mContext, mItems);
}
- if (strings == null || ((Collection) strings).isEmpty()) {
- return;
- }
- Set<String> newStrings = new HashSet<>(strings);
- Iterator<String> newStringsIter = newStrings.iterator();
- while (newStringsIter.hasNext()) {
- String encoded = newStringsIter.next();
- try {
- Decoder decoder = new Decoder(encoded, mContext);
- if (packageNames.contains(getIntentPackage(decoder.intent))
- && user.equals(decoder.user)) {
- newStringsIter.remove();
- }
- } catch (JSONException | URISyntaxException e) {
- Log.d(TAG, "Exception reading shortcut to add: " + e);
- newStringsIter.remove();
- }
- }
- sp.edit().putStringSet(APPS_PENDING_INSTALL, newStrings).apply();
}
/**
@@ -200,28 +169,14 @@
}
/**
- * Returns all pending shorts in the queue
+ * Returns a stream of all pending shortcuts in the queue
*/
@WorkerThread
- public HashSet<ShortcutKey> getPendingShortcuts() {
- HashSet<ShortcutKey> result = new HashSet<>();
-
- Set<String> strings = Utilities.getPrefs(mContext).getStringSet(APPS_PENDING_INSTALL, null);
- if (strings == null || ((Collection) strings).isEmpty()) {
- return result;
- }
-
- for (String encoded : strings) {
- try {
- Decoder decoder = new Decoder(encoded, mContext);
- if (decoder.optInt(Favorites.ITEM_TYPE, -1) == ITEM_TYPE_DEEP_SHORTCUT) {
- result.add(ShortcutKey.fromIntent(decoder.intent, decoder.user));
- }
- } catch (JSONException | URISyntaxException e) {
- Log.d(TAG, "Exception reading shortcut to add: " + e);
- }
- }
- return result;
+ public Stream<ShortcutKey> getPendingShortcuts(UserHandle user) {
+ ensureQueueLoaded();
+ return mItems.stream()
+ .filter(item -> item.itemType == ITEM_TYPE_DEEP_SHORTCUT && user.equals(item.user))
+ .map(item -> ShortcutKey.fromIntent(item.intent, user));
}
private void queuePendingShortcutInfo(PendingInstallShortcutInfo info) {
@@ -293,19 +248,9 @@
providerInfo = info;
}
- public String encodeToString(Context context) {
- try {
- return new JSONStringer()
- .object()
- .key(Favorites.ITEM_TYPE).value(itemType)
- .key(Favorites.INTENT).value(intent.toUri(0))
- .key(PROFILE_ID).value(
- UserCache.INSTANCE.get(context).getSerialNumberForUser(user))
- .endObject().toString();
- } catch (JSONException e) {
- Log.d(TAG, "Exception when adding shortcut: " + e);
- return null;
- }
+ @Override
+ public Intent getIntent() {
+ return intent;
}
public Pair<ItemInfo, Object> getItemInfo(Context context) {
@@ -365,55 +310,33 @@
? intent.getPackage() : intent.getComponent().getPackageName();
}
- private static PendingInstallShortcutInfo decode(String encoded, Context context) {
- try {
- Decoder decoder = new Decoder(encoded, context);
- switch (decoder.optInt(Favorites.ITEM_TYPE, -1)) {
- case Favorites.ITEM_TYPE_APPLICATION:
- return new PendingInstallShortcutInfo(
- decoder.intent.getPackage(), decoder.user);
- case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
- List<ShortcutInfo> si = ShortcutKey.fromIntent(decoder.intent, decoder.user)
- .buildRequest(context)
- .query(ShortcutRequest.ALL);
- if (si.isEmpty()) {
- return null;
- } else {
- return new PendingInstallShortcutInfo(si.get(0));
- }
+ private PendingInstallShortcutInfo decode(int itemType, UserHandle user, Intent intent) {
+ switch (itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ return new PendingInstallShortcutInfo(intent.getPackage(), user);
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
+ List<ShortcutInfo> si = ShortcutKey.fromIntent(intent, user)
+ .buildRequest(mContext)
+ .query(ShortcutRequest.ALL);
+ if (si.isEmpty()) {
+ return null;
+ } else {
+ return new PendingInstallShortcutInfo(si.get(0));
}
- case Favorites.ITEM_TYPE_APPWIDGET: {
- int widgetId = decoder.intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
- AppWidgetProviderInfo info =
- AppWidgetManager.getInstance(context).getAppWidgetInfo(widgetId);
- if (info == null || !info.provider.equals(decoder.intent.getComponent())
- || !info.getProfile().equals(decoder.user)) {
- return null;
- }
- return new PendingInstallShortcutInfo(info, widgetId);
- }
- default:
- Log.e(TAG, "Unknown item type");
}
- } catch (JSONException | URISyntaxException e) {
- Log.d(TAG, "Exception reading shortcut to add: " + e);
+ case Favorites.ITEM_TYPE_APPWIDGET: {
+ int widgetId = intent.getIntExtra(EXTRA_APPWIDGET_ID, 0);
+ AppWidgetProviderInfo info =
+ AppWidgetManager.getInstance(mContext).getAppWidgetInfo(widgetId);
+ if (info == null || !info.provider.equals(intent.getComponent())
+ || !info.getProfile().equals(user)) {
+ return null;
+ }
+ return new PendingInstallShortcutInfo(info, widgetId);
+ }
+ default:
+ Log.e(TAG, "Unknown item type");
}
return null;
}
-
- private static class Decoder extends JSONObject {
- public final Intent intent;
- public final UserHandle user;
-
- private Decoder(String encoded, Context context) throws JSONException, URISyntaxException {
- super(encoded);
- intent = Intent.parseUri(getString(Favorites.INTENT), 0);
- user = has(PROFILE_ID)
- ? UserCache.INSTANCE.get(context).getUserForSerialNumber(getLong(PROFILE_ID))
- : Process.myUserHandle();
- if (user == null || intent == null) {
- throw new JSONException("Invalid data");
- }
- }
- }
}
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
index ae20638..7ff2abb 100644
--- a/src/com/android/launcher3/util/PersistedItemArray.java
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -68,8 +68,7 @@
*/
@WorkerThread
public void write(Context context, List<T> items) {
- AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
+ AtomicFile file = getFile(context);
FileOutputStream fos;
try {
fos = file.startWrite();
@@ -124,9 +123,7 @@
@WorkerThread
public List<T> read(Context context, ItemFactory<T> factory, LongFunction<UserHandle> userFn) {
List<T> result = new ArrayList<>();
- AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
-
- try (FileInputStream fis = file.openRead()) {
+ try (FileInputStream fis = getFile(context).openRead()) {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
@@ -167,6 +164,13 @@
}
/**
+ * Returns the underlying file used for persisting data
+ */
+ public AtomicFile getFile(Context context) {
+ return new AtomicFile(context.getFileStreamPath(mFileName));
+ }
+
+ /**
* Interface to create an ItemInfo during parsing
*/
public interface ItemFactory<T extends ItemInfo> {