From 104c9d1206661b27d1ac4485de1bbb1cdcf4c898 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 12 Aug 2020 14:38:23 -0700 Subject: [PATCH] Using persisted item storage for install queue. Shared prefs are loaded during startup and should avoid large objects Change-Id: Ibb5c8307dbccb9414b42454825e6c3c2a972efa6 --- .../android/launcher3/model/BgDataModel.java | 3 +- .../launcher3/model/ItemInstallQueue.java | 205 ++++++------------ .../launcher3/util/PersistedItemArray.java | 14 +- 3 files changed, 74 insertions(+), 148 deletions(-) diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java index 7e45021bf3..cf3251416e 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -297,8 +297,7 @@ public class BgDataModel { .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 6238db3152..5e48a0ff2e 100644 --- a/src/com/android/launcher3/model/ItemInstallQueue.java +++ b/src/com/android/launcher3/model/ItemInstallQueue.java @@ -21,7 +21,6 @@ import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID; 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.appwidget.AppWidgetProviderInfo; 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.Launcher; 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 class ItemInstallQueue { 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 class ItemInstallQueue { public static MainThreadInitializedObject INSTANCE = new MainThreadInitializedObject<>(ItemInstallQueue::new); + private final PersistedItemArray 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 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 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 @@ public class ItemInstallQueue { // Launcher not loaded return; } - - ArrayList> installQueue = new ArrayList<>(); - SharedPreferences prefs = Utilities.getPrefs(mContext); - Set 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> 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 @@ public class ItemInstallQueue { if (packageNames.isEmpty()) { return; } - Preconditions.assertWorkerThread(); - - SharedPreferences sp = Utilities.getPrefs(mContext); - Set 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 newStrings = new HashSet<>(strings); - Iterator 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 @@ public class ItemInstallQueue { } /** - * Returns all pending shorts in the queue + * Returns a stream of all pending shortcuts in the queue */ @WorkerThread - public HashSet getPendingShortcuts() { - HashSet result = new HashSet<>(); - - Set 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 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 @@ public class ItemInstallQueue { 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 getItemInfo(Context context) { @@ -365,55 +310,33 @@ public class ItemInstallQueue { ? 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 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 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 ae20638e31..7ff2abb8a6 100644 --- a/src/com/android/launcher3/util/PersistedItemArray.java +++ b/src/com/android/launcher3/util/PersistedItemArray.java @@ -68,8 +68,7 @@ public class PersistedItemArray { */ @WorkerThread public void write(Context context, List items) { - AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName)); - + AtomicFile file = getFile(context); FileOutputStream fos; try { fos = file.startWrite(); @@ -124,9 +123,7 @@ public class PersistedItemArray { @WorkerThread public List read(Context context, ItemFactory factory, LongFunction userFn) { List 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)); @@ -166,6 +163,13 @@ public class PersistedItemArray { return result; } + /** + * 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 */