Caching last predictions and loading it with model
Adding support for persisting itemInfos on disk. This uses
a separate xml file. Unlike prefs, it does not keep the items
in memory and is just a wraper over reading/writing a file.
Bug: 160748731
Change-Id: Iaccab9928ab8f30127fb3c2d630ca8ca83f0bd05
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 432073e..f61bc05 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -658,7 +658,7 @@
}
}
- protected static void beginDocument(XmlPullParser parser, String firstElementName)
+ public static void beginDocument(XmlPullParser parser, String firstElementName)
throws XmlPullParserException, IOException {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 8b0ef7b..586333f 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -27,6 +27,7 @@
import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.PagedView;
import com.android.launcher3.model.BgDataModel.Callbacks;
+import com.android.launcher3.model.BgDataModel.FixedContainerItems;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -76,18 +77,20 @@
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
final IntArray orderedScreenIds = new IntArray();
+ ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
synchronized (mBgDataModel) {
workspaceItems.addAll(mBgDataModel.workspaceItems);
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
+ mBgDataModel.extraItems.forEach(extraItems::add);
mBgDataModel.lastBindId++;
mMyBindingId = mBgDataModel.lastBindId;
}
for (Callbacks cb : mCallbacksList) {
new WorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
- workspaceItems, appWidgets, orderedScreenIds).bind();
+ workspaceItems, appWidgets, extraItems, orderedScreenIds).bind();
}
}
@@ -135,7 +138,7 @@
private final ArrayList<ItemInfo> mWorkspaceItems;
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
private final IntArray mOrderedScreenIds;
-
+ private final ArrayList<FixedContainerItems> mExtraItems;
WorkspaceBinder(Callbacks callbacks,
Executor uiExecutor,
@@ -144,6 +147,7 @@
int myBindingId,
ArrayList<ItemInfo> workspaceItems,
ArrayList<LauncherAppWidgetInfo> appWidgets,
+ ArrayList<FixedContainerItems> extraItems,
IntArray orderedScreenIds) {
mCallbacks = callbacks;
mUiExecutor = uiExecutor;
@@ -152,6 +156,7 @@
mMyBindingId = myBindingId;
mWorkspaceItems = workspaceItems;
mAppWidgets = appWidgets;
+ mExtraItems = extraItems;
mOrderedScreenIds = orderedScreenIds;
}
@@ -198,6 +203,8 @@
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
bindAppWidgets(currentAppWidgets, mainExecutor);
+ mExtraItems.forEach(item ->
+ executeCallbacksTask(c -> c.bindExtraContainerItems(item), mainExecutor));
// Locate available spots for prediction using currentWorkspaceItems
IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
@@ -207,6 +214,7 @@
// happens later).
// This ensures that the first screen is immediately visible (eg. during rotation)
// In case of !validFirstPage, bind all pages one after other.
+
final Executor deferredExecutor =
validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index fd8520d..140342f 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -424,6 +424,14 @@
public FixedContainerItems clone() {
return new FixedContainerItems(containerId, new ArrayList<>(items));
}
+
+ public void setItems(List<ItemInfo> newItems) {
+ items.clear();
+ newItems.forEach(item -> {
+ item.container = containerId;
+ items.add(item);
+ });
+ }
}
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 165d1ea..a27ac23 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -65,7 +65,7 @@
private static final String TAG = "LoaderCursor";
- public final LongSparseArray<UserHandle> allUsers;
+ private final LongSparseArray<UserHandle> allUsers;
private final Uri mContentUri;
private final Context mContext;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index e89031e..1dd8c11 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -360,15 +360,12 @@
final int optionsIndex = c.getColumnIndexOrThrow(
LauncherSettings.Favorites.OPTIONS);
- final LongSparseArray<UserHandle> allUsers = c.allUsers;
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
mUserManagerState.init(mUserCache, mUserManager);
for (UserHandle user : mUserCache.getUserProfiles()) {
long serialNo = mUserCache.getSerialNumberForUser(user);
- allUsers.put(serialNo, user);
-
boolean userUnlocked = mUserManager.isUserUnlocked(user);
// We can only query for shortcuts when the user is unlocked.
@@ -419,16 +416,6 @@
ComponentName cn = intent.getComponent();
targetPkg = cn == null ? intent.getPackage() : cn.getPackageName();
- if (allUsers.indexOfValue(c.user) < 0) {
- if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
- c.markDeleted("Legacy shortcuts are only allowed for current users");
- continue;
- } else if (c.restoreFlag != 0) {
- // Don't restore items for other profiles.
- c.markDeleted("Restore from other profiles not supported");
- continue;
- }
- }
if (TextUtils.isEmpty(targetPkg) &&
c.itemType != LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
c.markDeleted("Only legacy shortcuts can have null package");
@@ -773,7 +760,7 @@
}
// Load delegate items
- mModelDelegate.loadItems();
+ mModelDelegate.loadItems(mUserManagerState, shortcutKeyToPinnedShortcuts);
// Break early if we've stopped loading
if (mStopped) {
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index ce4eed5..53e8a86 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -18,13 +18,17 @@
import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
+import android.content.pm.ShortcutInfo;
import androidx.annotation.WorkerThread;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
+import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.util.ResourceBasedOverride;
+import java.util.Map;
+
/**
* Class to extend LauncherModel functionality to provide extra data
*/
@@ -65,11 +69,12 @@
* Load delegate items if any in the data model
*/
@WorkerThread
- public void loadItems() { }
+ public void loadItems(UserManagerState ums, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts) { }
/**
* Called when the delegate is no loner needed
*/
@WorkerThread
public void destroy() { }
+
}
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index 59233cd..e03fd72 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -258,12 +258,6 @@
}
/**
- * Can be overridden by inherited classes to fill in {@link LauncherAtom.ItemInfo}
- */
- public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
- }
-
- /**
* Creates {@link LauncherAtom.ItemInfo} with important fields and parent container info.
*/
public LauncherAtom.ItemInfo buildProto() {
diff --git a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
index b0d19a6..c04b7f0 100644
--- a/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
+++ b/src/com/android/launcher3/model/data/LauncherAppWidgetInfo.java
@@ -26,7 +26,6 @@
import com.android.launcher3.AppWidgetResizeFrame;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.util.ContentWriter;
/**
@@ -195,13 +194,4 @@
public final boolean hasOptionFlag(int option) {
return (options & option) != 0;
}
-
- @Override
- public void setItemBuilder(LauncherAtom.ItemInfo.Builder builder) {
- builder.setWidget(LauncherAtom.Widget.newBuilder()
- .setSpanX(spanX)
- .setSpanY(spanY)
- .setComponentName(providerName.toString())
- .setPackageName(providerName.getPackageName()));
- }
}
diff --git a/src/com/android/launcher3/util/IOUtils.java b/src/com/android/launcher3/util/IOUtils.java
index fcb96d7..1cec0ec 100644
--- a/src/com/android/launcher3/util/IOUtils.java
+++ b/src/com/android/launcher3/util/IOUtils.java
@@ -16,20 +16,19 @@
package com.android.launcher3.util;
-import android.content.Context;
+import android.os.FileUtils;
import android.util.Log;
+import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.util.UUID;
/**
* Supports various IO utility functions
@@ -52,6 +51,9 @@
}
public static long copy(InputStream from, OutputStream to) throws IOException {
+ if (Utilities.ATLEAST_Q) {
+ return FileUtils.copy(from, to);
+ }
byte[] buf = new byte[BUF_SIZE];
long total = 0;
int r;
@@ -62,25 +64,6 @@
return total;
}
- /**
- * Utility method to debug binary data
- */
- public static String createTempFile(Context context, byte[] data) {
- if (!FeatureFlags.IS_STUDIO_BUILD) {
- throw new IllegalStateException("Method only allowed in development mode");
- }
-
- String name = UUID.randomUUID().toString();
- File file = new File(context.getCacheDir(), name);
- try (FileOutputStream fo = new FileOutputStream(file)) {
- fo.write(data);
- fo.flush();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- return file.getAbsolutePath();
- }
-
public static void closeSilently(Closeable c) {
if (c != null) {
try {
diff --git a/src/com/android/launcher3/util/PersistedItemArray.java b/src/com/android/launcher3/util/PersistedItemArray.java
new file mode 100644
index 0000000..ae20638
--- /dev/null
+++ b/src/com/android/launcher3/util/PersistedItemArray.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2020 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.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+import android.util.AtomicFile;
+import android.util.Log;
+import android.util.Xml;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.pm.UserCache;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.LongFunction;
+
+/**
+ * Utility class to read/write a list of {@link com.android.launcher3.model.data.ItemInfo} on disk.
+ * This class is not thread safe, the caller should ensure proper threading
+ */
+public class PersistedItemArray<T extends ItemInfo> {
+
+ private static final String TAG = "PersistedItemArray";
+
+ private static final String TAG_ROOT = "items";
+ private static final String TAG_ENTRY = "entry";
+
+ private final String mFileName;
+
+ public PersistedItemArray(String fileName) {
+ mFileName = fileName + ".xml";
+ }
+
+ /**
+ * Writes the provided list of items on the disk
+ */
+ @WorkerThread
+ public void write(Context context, List<T> items) {
+ AtomicFile file = new AtomicFile(context.getFileStreamPath(mFileName));
+
+ FileOutputStream fos;
+ try {
+ fos = file.startWrite();
+ } catch (IOException e) {
+ Log.e(TAG, "Unable to persist items in " + mFileName, e);
+ return;
+ }
+
+ UserCache userCache = UserCache.INSTANCE.get(context);
+
+ try {
+ XmlSerializer out = Xml.newSerializer();
+ out.setOutput(fos, StandardCharsets.UTF_8.name());
+ out.startDocument(null, true);
+ out.startTag(null, TAG_ROOT);
+ for (T item : items) {
+ Intent intent = item.getIntent();
+ if (intent == null) {
+ continue;
+ }
+
+ out.startTag(null, TAG_ENTRY);
+ out.attribute(null, Favorites.ITEM_TYPE, Integer.toString(item.itemType));
+ out.attribute(null, Favorites.PROFILE_ID,
+ Long.toString(userCache.getSerialNumberForUser(item.user)));
+ out.attribute(null, Favorites.INTENT, intent.toUri(0));
+ out.endTag(null, TAG_ENTRY);
+ }
+ out.endTag(null, TAG_ROOT);
+ out.endDocument();
+ } catch (IOException e) {
+ file.failWrite(fos);
+ Log.e(TAG, "Unable to persist items in " + mFileName, e);
+ return;
+ }
+
+ file.finishWrite(fos);
+ }
+
+ /**
+ * Reads the items from the disk
+ */
+ @WorkerThread
+ public List<T> read(Context context, ItemFactory<T> factory) {
+ return read(context, factory, UserCache.INSTANCE.get(context)::getUserForSerialNumber);
+ }
+
+ /**
+ * Reads the items from the disk
+ * @param userFn method to provide user handle for a given user serial
+ */
+ @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()) {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new InputStreamReader(fis, StandardCharsets.UTF_8));
+
+ AutoInstallsLayout.beginDocument(parser, TAG_ROOT);
+ final int depth = parser.getDepth();
+
+ int type;
+ while (((type = parser.next()) != XmlPullParser.END_TAG
+ || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+ if (type != XmlPullParser.START_TAG || !TAG_ENTRY.equals(parser.getName())) {
+ continue;
+ }
+ try {
+ int itemType = Integer.parseInt(
+ parser.getAttributeValue(null, Favorites.ITEM_TYPE));
+ UserHandle user = userFn.apply(Long.parseLong(
+ parser.getAttributeValue(null, Favorites.PROFILE_ID)));
+ Intent intent = Intent.parseUri(
+ parser.getAttributeValue(null, Favorites.INTENT), 0);
+
+ if (user != null && intent != null) {
+ T item = factory.createInfo(itemType, user, intent);
+ if (item != null) {
+ result.add(item);
+ }
+ }
+ } catch (Exception e) {
+ // Ignore this entry
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Ignore
+ } catch (IOException | XmlPullParserException e) {
+ Log.e(TAG, "Unable to read items in " + mFileName, e);
+ return Collections.emptyList();
+ }
+ return result;
+ }
+
+ /**
+ * Interface to create an ItemInfo during parsing
+ */
+ public interface ItemFactory<T extends ItemInfo> {
+
+ /**
+ * Returns an item info or null in which case the entry is ignored
+ */
+ @Nullable
+ T createInfo(int itemType, UserHandle user, Intent intent);
+ }
+}