Improve Hybird hotseat cache support
Loads list of cached apps and shows predicted app icons with the rest of workspace items.
-> Extracted prediction caching logic into a model layer
Bug: 155029837
Test: Manual
Change-Id: I6981594a910f5fe4e8e8cf8fe39db0cb856e7acd
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index c75bd95..1d9c0c3 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -2225,6 +2225,9 @@
workspace.requestLayout();
}
+ @Override
+ public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
+
/**
* Add the views for a widget to the workspace.
*/
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 2f38037..14e604d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -33,6 +33,7 @@
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.icons.IconProvider;
import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.model.PredictionModel;
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.pm.InstallSessionHelper;
import com.android.launcher3.pm.InstallSessionTracker;
@@ -57,6 +58,7 @@
private final IconCache mIconCache;
private final WidgetPreviewLoader mWidgetCache;
private final InvariantDeviceProfile mInvariantDeviceProfile;
+ private final PredictionModel mPredictionModel;
private SecureSettingsObserver mNotificationDotsObserver;
private InstallSessionTracker mInstallSessionTracker;
@@ -127,6 +129,7 @@
mIconCache = new IconCache(mContext, mInvariantDeviceProfile, iconCacheFileName);
mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache);
mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
+ mPredictionModel = new PredictionModel(mContext);
}
protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
@@ -182,6 +185,10 @@
return mModel;
}
+ public PredictionModel getPredictionModel() {
+ return mPredictionModel;
+ }
+
public WidgetPreviewLoader getWidgetCache() {
return mWidgetCache;
}
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index c98be56..1465100 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -17,6 +17,7 @@
package com.android.launcher3.model;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import android.util.Log;
@@ -196,6 +197,10 @@
// Load items on the current page.
bindWorkspaceItems(currentWorkspaceItems, mainExecutor);
bindAppWidgets(currentAppWidgets, mainExecutor);
+
+ // Locate available spots for prediction using currentWorkspaceItems
+ IntArray gaps = getMissingHotseatRanks(currentWorkspaceItems, idp.numHotseatIcons);
+ bindPredictedItems(gaps, mainExecutor);
// In case of validFirstPage, only bind the first screen, and defer binding the
// remaining screens after first onDraw (and an optional the fade animation whichever
// happens later).
@@ -247,6 +252,11 @@
}
}
+ private void bindPredictedItems(IntArray ranks, final Executor executor) {
+ executeCallbacksTask(
+ c -> c.bindPredictedItems(mBgDataModel.cachedPredictedItems, ranks), executor);
+ }
+
protected void executeCallbacksTask(CallbackTask task, Executor executor) {
executor.execute(() -> {
if (mMyBindingId != mBgDataModel.lastBindId) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index f79a9d1..2522a49 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -93,6 +93,11 @@
public final Map<ShortcutKey, MutableInt> pinnedShortcutCounts = new HashMap<>();
/**
+ * List of all cached predicted items visible on home screen
+ */
+ public final ArrayList<AppInfo> cachedPredictedItems = new ArrayList<>();
+
+ /**
* True if the launcher has permission to access deep shortcuts.
*/
public boolean hasShortcutHostPermission;
@@ -366,5 +371,10 @@
void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap);
void bindAllApplications(AppInfo[] apps);
+
+ /**
+ * Binds predicted appInfos at at available prediction slots.
+ */
+ void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks);
}
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 4c02837..90aaf44 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -179,6 +179,7 @@
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
+ loadCachedPredictions();
logger.addSplit("loadWorkspace");
verifyNotStopped();
@@ -849,6 +850,23 @@
}
}
+ private List<AppInfo> loadCachedPredictions() {
+ List<ComponentKey> componentKeys = mApp.getPredictionModel().getPredictionComponentKeys();
+ List<AppInfo> results = new ArrayList<>();
+ if (componentKeys == null) return results;
+ List<LauncherActivityInfo> l;
+ mBgDataModel.cachedPredictedItems.clear();
+ for (ComponentKey key : componentKeys) {
+ l = mLauncherApps.getActivityList(key.componentName.getPackageName(), key.user);
+ if (l.size() == 0) continue;
+ boolean quietMode = mUserManager.isQuietModeEnabled(key.user);
+ AppInfo info = new AppInfo(l.get(0), key.user, quietMode);
+ mBgDataModel.cachedPredictedItems.add(info);
+ mIconCache.getTitleAndIcon(info, false);
+ }
+ return results;
+ }
+
private List<LauncherActivityInfo> loadAllApps() {
final List<UserHandle> profiles = mUserCache.getUserProfiles();
List<LauncherActivityInfo> allActivityList = new ArrayList<>();
@@ -880,6 +898,14 @@
PackageInstallInfo.fromInstallingState(info));
}
}
+ for (AppInfo item : mBgDataModel.cachedPredictedItems) {
+ List<LauncherActivityInfo> l = mLauncherApps.getActivityList(
+ item.componentName.getPackageName(), item.user);
+ for (LauncherActivityInfo info : l) {
+ boolean quietMode = mUserManager.isQuietModeEnabled(item.user);
+ mBgAllAppsList.add(new AppInfo(info, item.user, quietMode), info);
+ }
+ }
mBgAllAppsList.getAndResetChangeFlag();
return allActivityList;
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index ef7e828..4efeba5 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -19,11 +19,14 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
+import java.util.List;
+import java.util.stream.IntStream;
/**
* Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -109,4 +112,17 @@
}
});
}
+
+ /**
+ * Iterates though current workspace items and returns available hotseat ranks for prediction.
+ */
+ public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
+ IntSet seen = new IntSet();
+ items.stream().filter(
+ info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
+ .forEach(i -> seen.add(i.screenId));
+ IntArray result = new IntArray(len);
+ IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
+ return result;
+ }
}
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
new file mode 100644
index 0000000..6aa41eb
--- /dev/null
+++ b/src/com/android/launcher3/model/PredictionModel.java
@@ -0,0 +1,93 @@
+/*
+ * 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.model;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.UserHandle;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.util.ComponentKey;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Model helper for app predictions in workspace
+ */
+public class PredictionModel {
+ private static final String CACHED_ITEMS_KEY = "predicted_item_keys";
+ private static final int MAX_CACHE_ITEMS = 5;
+
+ private final Context mContext;
+ private final SharedPreferences mDevicePrefs;
+ private ArrayList<ComponentKey> mCachedComponentKeys;
+
+ public PredictionModel(Context context) {
+ mContext = context;
+ mDevicePrefs = Utilities.getDevicePrefs(mContext);
+ }
+
+ /**
+ * Formats and stores a list of component key in device preferences.
+ */
+ public void cachePredictionComponentKeys(List<ComponentKey> componentKeys) {
+ StringBuilder builder = new StringBuilder();
+ int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
+ for (int i = 0; i < count; i++) {
+ builder.append(componentKeys.get(i));
+ builder.append("\n");
+ }
+ mDevicePrefs.edit().putString(CACHED_ITEMS_KEY, builder.toString()).apply();
+ mCachedComponentKeys = null;
+ }
+
+ /**
+ * parses and returns ComponentKeys saved by
+ * {@link PredictionModel#cachePredictionComponentKeys(List)}
+ */
+ public List<ComponentKey> getPredictionComponentKeys() {
+ if (mCachedComponentKeys == null) {
+ mCachedComponentKeys = new ArrayList<>();
+
+ String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
+ for (String line : cachedBlob.split("\n")) {
+ ComponentKey key = ComponentKey.fromString(line);
+ if (key != null) {
+ mCachedComponentKeys.add(key);
+ }
+ }
+ }
+ return mCachedComponentKeys;
+ }
+
+ /**
+ * Remove uninstalled applications from model
+ */
+ public void removePackage(String pkgName, UserHandle user, ArrayList<AppInfo> ids) {
+ for (int i = ids.size() - 1; i >= 0; i--) {
+ AppInfo info = ids.get(i);
+ if (info.user.equals(user) && pkgName.equals(info.componentName.getPackageName())) {
+ ids.remove(i);
+ }
+ }
+ cachePredictionComponentKeys(getPredictionComponentKeys().stream()
+ .filter(cn -> !(cn.user.equals(user) && cn.componentName.getPackageName().equals(
+ pkgName))).collect(Collectors.toList()));
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index dd6fc49..4a15af1 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -196,6 +196,9 @@
public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
@Override
+ public void bindPredictedItems(List<AppInfo> appInfos, IntArray ranks) { }
+
+ @Override
public void bindScreens(IntArray orderedScreenIds) { }
@Override