Switch to new protocol for hybrid hotseat

- create predictor from items in bgModel instead of scanning views
- Launcher no longer checks for duplicates before sending pin/unpin events
- sending cached items from last prediction to reduce UI shuffle
- Switch to using UserCache to persist and read ComponentKey

Bug: 148814143
Bug: 156413231
Bug: 156200931
Change-Id: Ide6330bed8eb7f0c6fbec1d1ac21e7f67a9b2be2
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 0b0983c..c1aed98 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -110,6 +110,18 @@
 
             return res;
         }
+
+
+        /**
+         * This is used to determine if an object is dropped at a different location than it was
+         * dragged from
+         */
+        public boolean isMoved() {
+            return dragInfo.cellX != originalDragInfo.cellX
+                    || dragInfo.cellY != originalDragInfo.cellY
+                    || dragInfo.screenId != originalDragInfo.screenId
+                    || dragInfo.container != originalDragInfo.container;
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 14e604d..53e5274 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -129,7 +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);
+        mPredictionModel = PredictionModel.newInstance(mContext);
     }
 
     protected void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 1465100..ab921ea 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -253,8 +253,8 @@
         }
 
         private void bindPredictedItems(IntArray ranks, final Executor executor) {
-            executeCallbacksTask(
-                    c -> c.bindPredictedItems(mBgDataModel.cachedPredictedItems, ranks), executor);
+            ArrayList<AppInfo> items = new ArrayList<>(mBgDataModel.cachedPredictedItems);
+            executeCallbacksTask(c -> c.bindPredictedItems(items, ranks), executor);
         }
 
         protected void executeCallbacksTask(CallbackTask task, Executor executor) {
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 9e6282e..d05d70b 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -45,6 +45,8 @@
 import android.util.MutableInt;
 import android.util.TimingLogger;
 
+import androidx.annotation.WorkerThread;
+
 import com.android.launcher3.InstallShortcutReceiver;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
@@ -850,12 +852,11 @@
         }
     }
 
-    private List<AppInfo> loadCachedPredictions() {
+    @WorkerThread
+    private void loadCachedPredictions() {
         synchronized (mBgDataModel) {
             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) {
@@ -866,7 +867,6 @@
                 mBgDataModel.cachedPredictedItems.add(info);
                 mIconCache.getTitleAndIcon(info, false);
             }
-            return results;
         }
     }
 
diff --git a/src/com/android/launcher3/model/PredictionModel.java b/src/com/android/launcher3/model/PredictionModel.java
index 6aa41eb..f8140eb 100644
--- a/src/com/android/launcher3/model/PredictionModel.java
+++ b/src/com/android/launcher3/model/PredictionModel.java
@@ -14,60 +14,86 @@
  * limitations under the License.
  */
 package com.android.launcher3.model;
+import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.UserHandle;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Preconditions;
+import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
- * Model helper for app predictions in workspace
+ * Model Helper for app predictions
  */
-public class PredictionModel {
+public class PredictionModel implements ResourceBasedOverride {
+
     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;
+    protected Context mContext;
     private ArrayList<ComponentKey> mCachedComponentKeys;
+    private SharedPreferences mDevicePrefs;
+    private UserCache mUserCache;
 
-    public PredictionModel(Context context) {
-        mContext = context;
-        mDevicePrefs = Utilities.getDevicePrefs(mContext);
+
+    /**
+     * Retrieve instance of this object that can be overridden in runtime based on the build
+     * variant of the application.
+     */
+    public static PredictionModel newInstance(Context context) {
+        PredictionModel model = Overrides.getObject(PredictionModel.class, context,
+                R.string.prediction_model_class);
+        model.init(context);
+        return model;
     }
 
+    protected void init(Context context) {
+        mContext = context;
+        mDevicePrefs = Utilities.getDevicePrefs(mContext);
+        mUserCache = UserCache.INSTANCE.get(mContext);
+
+    }
     /**
      * Formats and stores a list of component key in device preferences.
      */
+    @AnyThread
     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;
+        MODEL_EXECUTOR.execute(() -> {
+            StringBuilder builder = new StringBuilder();
+            int count = Math.min(componentKeys.size(), MAX_CACHE_ITEMS);
+            for (int i = 0; i < count; i++) {
+                builder.append(serializeComponentKeyToString(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)}
      */
+    @WorkerThread
     public List<ComponentKey> getPredictionComponentKeys() {
+        Preconditions.assertWorkerThread();
         if (mCachedComponentKeys == null) {
             mCachedComponentKeys = new ArrayList<>();
-
             String cachedBlob = mDevicePrefs.getString(CACHED_ITEMS_KEY, "");
             for (String line : cachedBlob.split("\n")) {
-                ComponentKey key = ComponentKey.fromString(line);
+                ComponentKey key = getComponentKeyFromSerializedString(line);
                 if (key != null) {
                     mCachedComponentKeys.add(key);
                 }
@@ -76,18 +102,26 @@
         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);
-            }
+    private String serializeComponentKeyToString(ComponentKey componentKey) {
+        long userSerialNumber = mUserCache.getSerialNumberForUser(componentKey.user);
+        return componentKey.componentName.flattenToString() + "#" + userSerialNumber;
+    }
+
+    private ComponentKey getComponentKeyFromSerializedString(String str) {
+        int sep = str.indexOf('#');
+        if (sep < 0 || (sep + 1) >= str.length()) {
+            return null;
         }
-        cachePredictionComponentKeys(getPredictionComponentKeys().stream()
-                .filter(cn -> !(cn.user.equals(user) && cn.componentName.getPackageName().equals(
-                        pkgName))).collect(Collectors.toList()));
+        ComponentName componentName = ComponentName.unflattenFromString(str.substring(0, sep));
+        if (componentName == null) {
+            return null;
+        }
+        try {
+            long serialNumber = Long.parseLong(str.substring(sep + 1));
+            UserHandle userHandle = mUserCache.getUserForSerialNumber(serialNumber);
+            return userHandle != null ? new ComponentKey(componentName, userHandle) : null;
+        } catch (NumberFormatException ex) {
+            return null;
+        }
     }
 }