Fixing bug when model was not reflected properly on the UI.
If launcher submits a job, and then reloads before the job is executed, the correct model
is not reflected on the Launcher. In that case, we simply rebind the launcher

Change-Id: I380242a4de13e7b2bc326d1a076f0a974435999c
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index a1176f1..fdf468c 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -400,17 +400,22 @@
             getStateManager().reapplyState();
 
             // TODO: We can probably avoid rebind when only screen size changed.
-            int currentPage = mWorkspace.getNextPage();
-            if (mModel.startLoader(currentPage)) {
-                mWorkspace.setCurrentPage(currentPage);
-                setWorkspaceLoading(true);
-            }
+            rebindModel();
         }
 
         mOldConfig.setTo(newConfig);
         super.onConfigurationChanged(newConfig);
     }
 
+    @Override
+    public void rebindModel() {
+        int currentPage = mWorkspace.getNextPage();
+        if (mModel.startLoader(currentPage)) {
+            mWorkspace.setCurrentPage(currentPage);
+            setWorkspaceLoading(true);
+        }
+    }
+
     private void initDeviceProfile(InvariantDeviceProfile idp) {
         // Load configuration-specific DeviceProfile
         mDeviceProfile = idp.getDeviceProfile(this);
@@ -420,7 +425,7 @@
             display.getSize(mwSize);
             mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
         }
-        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
+        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout(), true);
     }
 
     public RotationHelper getRotationHelper() {
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 6646b78..04a32f7 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -135,6 +135,8 @@
     };
 
     public interface Callbacks {
+        public void rebindModel();
+
         public int getCurrentWorkspaceScreen();
         public void clearPendingBinds();
         public void startBinding();
@@ -196,8 +198,9 @@
         enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
     }
 
-    public ModelWriter getWriter(boolean hasVerticalHotseat) {
-        return new ModelWriter(mApp.getContext(), sBgDataModel, hasVerticalHotseat);
+    public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
+        return new ModelWriter(mApp.getContext(), this, sBgDataModel,
+                hasVerticalHotseat, verifyChanges);
     }
 
     static void checkItemInfoLocked(
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index 9aa30e7..fcdc088 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -29,7 +29,6 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.widget.WidgetListRowEntry;
-import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
@@ -80,19 +79,18 @@
      */
     public final void scheduleCallbackTask(final CallbackTask task) {
         final Callbacks callbacks = mModel.getCallback();
-        mUiExecutor.execute(new Runnable() {
-            public void run() {
-                Callbacks cb = mModel.getCallback();
-                if (callbacks == cb && cb != null) {
-                    task.execute(callbacks);
-                }
+        mUiExecutor.execute(() -> {
+            Callbacks cb = mModel.getCallback();
+            if (callbacks == cb && cb != null) {
+                task.execute(callbacks);
             }
         });
     }
 
     public ModelWriter getModelWriter() {
-        // Updates from model task, do not deal with icon position in hotseat.
-        return mModel.getWriter(false /* hasVerticalHotseat */);
+        // Updates from model task, do not deal with icon position in hotseat. Also no need to
+        // verify changes as the ModelTasks always push the changes to callbacks
+        return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */);
     }
 
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index 8640401..fff1e69 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -106,6 +106,11 @@
     public final WidgetsModel widgetsModel = new WidgetsModel();
 
     /**
+     * Id when the model was last bound
+     */
+    public int lastBindId = 0;
+
+    /**
      * Clears all the data
      */
     public synchronized void clear() {
diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java
index 5acc790..5d4a352 100644
--- a/src/com/android/launcher3/model/LoaderResults.java
+++ b/src/com/android/launcher3/model/LoaderResults.java
@@ -25,7 +25,6 @@
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetInfo;
-import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.MainThreadExecutor;
@@ -37,7 +36,6 @@
 import com.android.launcher3.util.MultiHashMap;
 import com.android.launcher3.util.ViewOnDrawExecutor;
 import com.android.launcher3.widget.WidgetListRowEntry;
-import com.android.launcher3.widget.WidgetsListAdapter;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -100,6 +98,7 @@
             workspaceItems.addAll(mBgDataModel.workspaceItems);
             appWidgets.addAll(mBgDataModel.appWidgets);
             orderedScreenIds.addAll(mBgDataModel.workspaceScreens);
+            mBgDataModel.lastBindId++;
         }
 
         final int currentScreen;
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 40e0f49..72c703b 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -21,12 +21,15 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.Log;
 
 import com.android.launcher3.FolderInfo;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.Callbacks;
 import com.android.launcher3.LauncherProvider;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -50,15 +53,23 @@
     public static final boolean DEBUG_DELETE = true;
 
     private final Context mContext;
+    private final LauncherModel mModel;
     private final BgDataModel mBgDataModel;
+    private final Handler mUiHandler;
+
     private final Executor mWorkerExecutor;
     private final boolean mHasVerticalHotseat;
+    private final boolean mVerifyChanges;
 
-    public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
+    public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
+            boolean hasVerticalHotseat, boolean verifyChanges) {
         mContext = context;
+        mModel = model;
         mBgDataModel = dataModel;
         mWorkerExecutor = new LooperExecutor(LauncherModel.getWorkerLooper());
         mHasVerticalHotseat = hasVerticalHotseat;
+        mVerifyChanges = verifyChanges;
+        mUiHandler = new Handler(Looper.getMainLooper());
     }
 
     private void updateItemInfoProps(
@@ -214,15 +225,16 @@
         item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
         writer.put(Favorites._ID, item.id);
 
-        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
-        mWorkerExecutor.execute(new Runnable() {
-            public void run() {
-                cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+        ModelVerifier verifier = new ModelVerifier();
 
-                synchronized (mBgDataModel) {
-                    checkItemInfoLocked(item.id, item, stackTrace);
-                    mBgDataModel.addItem(mContext, item, true);
-                }
+        final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+        mWorkerExecutor.execute(() -> {
+            cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+
+            synchronized (mBgDataModel) {
+                checkItemInfoLocked(item.id, item, stackTrace);
+                mBgDataModel.addItem(mContext, item, true);
+                verifier.verifyModel();
             }
         });
     }
@@ -253,6 +265,7 @@
             }
             FileLog.d(TAG, "Finished deleting items");
         }
+        ModelVerifier verifier = new ModelVerifier();
 
         mWorkerExecutor.execute(() -> {
             for (ItemInfo item : items) {
@@ -260,6 +273,7 @@
                 mContext.getContentResolver().delete(uri, null, null);
 
                 mBgDataModel.removeItem(mContext, item);
+                verifier.verifyModel();
             }
         });
     }
@@ -273,6 +287,8 @@
             FileLog.d(TAG, "Deleting folder " + info, new Exception());
         }
 
+        ModelVerifier verifier = new ModelVerifier();
+
         mWorkerExecutor.execute(() -> {
             ContentResolver cr = mContext.getContentResolver();
             cr.delete(LauncherSettings.Favorites.CONTENT_URI,
@@ -282,6 +298,7 @@
 
             cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
             mBgDataModel.removeItem(mContext, info);
+            verifier.verifyModel();
         });
     }
 
@@ -336,6 +353,7 @@
 
     private abstract class UpdateItemBaseRunnable implements Runnable {
         private final StackTraceElement[] mStackTrace;
+        private final ModelVerifier mVerifier = new ModelVerifier();
 
         UpdateItemBaseRunnable() {
             mStackTrace = new Throwable().getStackTrace();
@@ -380,7 +398,45 @@
                 } else {
                     mBgDataModel.workspaceItems.remove(modelItem);
                 }
+                mVerifier.verifyModel();
             }
         }
     }
+
+    /**
+     * Utility class to verify model updates are propagated properly to the callback.
+     */
+    public class ModelVerifier {
+
+        final int startId;
+
+        ModelVerifier() {
+            startId = mBgDataModel.lastBindId;
+        }
+
+        void verifyModel() {
+            if (!mVerifyChanges || mModel.getCallback() == null) {
+                return;
+            }
+
+            int executeId = mBgDataModel.lastBindId;
+
+            mUiHandler.post(() -> {
+                int currentId = mBgDataModel.lastBindId;
+                if (currentId > executeId) {
+                    // Model was already bound after job was executed.
+                    return;
+                }
+                if (executeId == startId) {
+                    // Bound model has not changed during the job
+                    return;
+                }
+                // Bound model was changed between submitting the job and executing the job
+                Callbacks callbacks = mModel.getCallback();
+                if (callbacks != null) {
+                    callbacks.rebindModel();
+                }
+            });
+        }
+    }
 }