diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index a1176f1800..fdf468c98d 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -400,17 +400,22 @@ public class Launcher extends BaseActivity 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 @@ public class Launcher extends BaseActivity 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 6646b78de4..04a32f7c96 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -135,6 +135,8 @@ public class LauncherModel extends BroadcastReceiver }; public interface Callbacks { + public void rebindModel(); + public int getCurrentWorkspaceScreen(); public void clearPendingBinds(); public void startBinding(); @@ -196,8 +198,9 @@ public class LauncherModel extends BroadcastReceiver 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 9aa30e7cca..fcdc088a70 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.ComponentKey; 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 abstract class BaseModelUpdateTask implements ModelUpdateTask { */ 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 8640401328..fff1e69913 100644 --- a/src/com/android/launcher3/model/BgDataModel.java +++ b/src/com/android/launcher3/model/BgDataModel.java @@ -105,6 +105,11 @@ public class BgDataModel { */ public final WidgetsModel widgetsModel = new WidgetsModel(); + /** + * Id when the model was last bound + */ + public int lastBindId = 0; + /** * Clears all the data */ diff --git a/src/com/android/launcher3/model/LoaderResults.java b/src/com/android/launcher3/model/LoaderResults.java index 5acc790b83..5d4a352994 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.InvariantDeviceProfile; 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.LooperIdleLock; 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 @@ public class LoaderResults { 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 40e0f49a86..72c703b2e5 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -21,12 +21,15 @@ import android.content.ContentResolver; 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 class ModelWriter { 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 @@ public class ModelWriter { 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 @@ public class ModelWriter { } FileLog.d(TAG, "Finished deleting items"); } + ModelVerifier verifier = new ModelVerifier(); mWorkerExecutor.execute(() -> { for (ItemInfo item : items) { @@ -260,6 +273,7 @@ public class ModelWriter { mContext.getContentResolver().delete(uri, null, null); mBgDataModel.removeItem(mContext, item); + verifier.verifyModel(); } }); } @@ -273,6 +287,8 @@ public class ModelWriter { 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 @@ public class ModelWriter { cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null); mBgDataModel.removeItem(mContext, info); + verifier.verifyModel(); }); } @@ -336,6 +353,7 @@ public class ModelWriter { 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 @@ public class ModelWriter { } 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(); + } + }); + } + } } diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index cf90afd4b1..b217847f5e 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -1,5 +1,11 @@ package com.android.launcher3.model; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -24,8 +30,8 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherModel.Callbacks; +import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherProvider; import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.util.ComponentKey; @@ -43,12 +49,6 @@ import java.util.HashMap; import java.util.List; import java.util.concurrent.Executor; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - /** * Base class for writing tests for Model update tasks. */ @@ -82,7 +82,7 @@ public class BaseModelUpdateTaskTestCase { modelWriter = mock(ModelWriter.class); when(appState.getModel()).thenReturn(model); - when(model.getWriter(anyBoolean())).thenReturn(modelWriter); + when(model.getWriter(anyBoolean(), anyBoolean())).thenReturn(modelWriter); when(model.getCallback()).thenReturn(callbacks); myUser = Process.myUserHandle();