Merge "Cache hotseat predictions" into ub-launcher3-rvc-dev
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
index fdb8e4c..d200868 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/ComponentKeyMapper.java
@@ -21,7 +21,6 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
-import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ComponentKey;
 
 public class ComponentKeyMapper {
@@ -57,9 +56,8 @@
             return item;
         } else if (getComponentClass().equals(COMPONENT_CLASS_MARKER)) {
             return mCache.getInstantApp(componentKey.componentName.getPackageName());
-        } else if (componentKey instanceof ShortcutKey) {
-            return mCache.getShortcutInfo((ShortcutKey) componentKey);
+        } else {
+            return mCache.getShortcutInfo(componentKey);
         }
-        return null;
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
index 6c4bfe8..ab96b1340 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/DynamicItemCache.java
@@ -45,6 +45,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.InstantAppResolver;
 
 import java.util.ArrayList;
@@ -76,7 +77,7 @@
     private final Runnable mOnUpdateCallback;
     private final IconCache mIconCache;
 
-    private final Map<ShortcutKey, WorkspaceItemInfo> mShortcuts;
+    private final Map<ComponentKey, WorkspaceItemInfo> mShortcuts;
     private final Map<String, InstantAppItemInfo> mInstantApps;
 
     public DynamicItemCache(Context context, Runnable onUpdateCallback) {
@@ -230,7 +231,7 @@
     }
 
     @MainThread
-    public WorkspaceItemInfo getShortcutInfo(ShortcutKey key) {
+    public WorkspaceItemInfo getShortcutInfo(ComponentKey key) {
         return mShortcuts.get(key);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 077a1ad..1aff8e9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -98,6 +98,8 @@
     //TODO: replace this with AppTargetEvent.ACTION_UNPIN (b/144119543)
     private static final int APPTARGET_ACTION_UNPIN = 4;
 
+    private static final String PREDICTED_ITEMS_CACHE_KEY = "predicted_item_keys";
+
     private static final String APP_LOCATION_HOTSEAT = "hotseat";
     private static final String APP_LOCATION_WORKSPACE = "workspace";
 
@@ -122,6 +124,7 @@
     private AllAppsStore mAllAppsStore;
     private AnimatorSet mIconRemoveAnimators;
     private boolean mUIUpdatePaused = false;
+    private boolean mRequiresCacheUpdate = false;
 
     private HotseatEduController mHotseatEduController;
 
@@ -148,6 +151,7 @@
         if (mHotseat.isAttachedToWindow()) {
             onViewAttachedToWindow(mHotseat);
         }
+        showCachedItems();
     }
 
     /**
@@ -297,6 +301,16 @@
         mAppPredictor.requestPredictionUpdate();
     }
 
+    private void showCachedItems() {
+        ArrayList<ComponentKey> componentKeys = getCachedComponentKeys();
+        mComponentKeyMappers.clear();
+        for (ComponentKey key : componentKeys) {
+            mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
+        }
+        updateDependencies();
+        fillGapsWithPrediction();
+    }
+
     private Bundle getAppPredictionContextExtra() {
         Bundle bundle = new Bundle();
 
@@ -353,6 +367,7 @@
     private void setPredictedApps(List<AppTarget> appTargets) {
         mComponentKeyMappers.clear();
         StringBuilder predictionLog = new StringBuilder("predictedApps: [\n");
+        ArrayList<ComponentKey> componentKeys = new ArrayList<>();
         for (AppTarget appTarget : appTargets) {
             ComponentKey key;
             if (appTarget.getShortcutInfo() != null) {
@@ -361,6 +376,7 @@
                 key = new ComponentKey(new ComponentName(appTarget.getPackageName(),
                         appTarget.getClassName()), appTarget.getUser());
             }
+            componentKeys.add(key);
             predictionLog.append(key.toString());
             predictionLog.append(",rank:");
             predictionLog.append(appTarget.getRank());
@@ -375,6 +391,35 @@
         } else if (mHotseatEduController != null) {
             mHotseatEduController.setPredictedApps(mapToWorkspaceItemInfo(mComponentKeyMappers));
         }
+        // should invalidate cache if AiAi sends empty list of AppTargets
+        if (appTargets.isEmpty()) {
+            mRequiresCacheUpdate = true;
+        }
+        cachePredictionComponentKeys(componentKeys);
+    }
+
+    private void cachePredictionComponentKeys(ArrayList<ComponentKey> componentKeys) {
+        if (!mRequiresCacheUpdate) return;
+        StringBuilder builder = new StringBuilder();
+        for (ComponentKey componentKey : componentKeys) {
+            builder.append(componentKey);
+            builder.append("\n");
+        }
+        mLauncher.getDevicePrefs().edit().putString(PREDICTED_ITEMS_CACHE_KEY,
+                builder.toString()).apply();
+        mRequiresCacheUpdate = false;
+    }
+
+    private ArrayList<ComponentKey> getCachedComponentKeys() {
+        String cachedBlob = mLauncher.getDevicePrefs().getString(PREDICTED_ITEMS_CACHE_KEY, "");
+        ArrayList<ComponentKey> results = new ArrayList<>();
+        for (String line : cachedBlob.split("\n")) {
+            ComponentKey key = ComponentKey.fromString(line);
+            if (key != null) {
+                results.add(key);
+            }
+        }
+        return results;
     }
 
     private void updateDependencies() {
@@ -400,6 +445,7 @@
         icon.pin(workspaceItemInfo);
         AppTarget appTarget = getAppTargetFromItemInfo(workspaceItemInfo);
         notifyItemAction(appTarget, APP_LOCATION_HOTSEAT, AppTargetEvent.ACTION_PIN);
+        mRequiresCacheUpdate = true;
     }
 
     private List<WorkspaceItemInfo> mapToWorkspaceItemInfo(
@@ -566,6 +612,7 @@
         }
         mDragObject = null;
         fillGapsWithPrediction(true, this::removeOutlineDrawings);
+        mRequiresCacheUpdate = true;
     }
 
     @Nullable