Binding Taskbar directly from Launcher model
This allows taskbar to be loaded even in case of 3P Launchers
and removes dependency on LauncherActivity lifecycle
Bug: 187353581
Bug: 188788621
Test: Manual
Change-Id: I5a0988e0697b41677d4c58f0213aef14ec0c0972
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 00278e4..f4447b1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -2147,7 +2147,7 @@
mShakeAnimators.clear();
}
- private void commitTempPlacement() {
+ private void commitTempPlacement(View dragView) {
mTmpOccupied.copyTo(mOccupied);
int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
@@ -2165,7 +2165,7 @@
ItemInfo info = (ItemInfo) child.getTag();
// We do a null check here because the item info can be null in the case of the
// AllApps button in the hotseat.
- if (info != null) {
+ if (info != null && child != dragView) {
final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
|| info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
|| info.spanY != lp.cellVSpan);
@@ -2327,7 +2327,7 @@
animateItemsToSolution(swapSolution, dragView, commit);
if (commit) {
- commitTempPlacement();
+ commitTempPlacement(null);
completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
@@ -2421,7 +2421,8 @@
if (!DESTRUCTIVE_REORDER &&
(mode == MODE_ON_DROP || mode == MODE_ON_DROP_EXTERNAL)) {
- commitTempPlacement();
+ // Since the temp solution didn't update dragView, don't commit it either
+ commitTempPlacement(dragView);
completeAndClearReorderPreviewAnimations();
setItemPlacementDirty(false);
} else {
@@ -2877,7 +2878,7 @@
directionVector, null, false, configuration).isSolution) {
if (commitConfig) {
copySolutionToTempState(configuration, null);
- commitTempPlacement();
+ commitTempPlacement(null);
// undo marking cells occupied since there is actually nothing being placed yet.
mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 89b44a3..78a8a97 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -48,7 +48,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_RECONFIGURED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_ACTIVITY_PAUSED;
import static com.android.launcher3.model.ItemInstallQueue.FLAG_DRAG_AND_DROP;
-import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
@@ -596,7 +595,7 @@
}
onDeviceProfileInitiated();
- mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true);
+ mModelWriter = mModel.getWriter(getDeviceProfile().isVerticalBarLayout(), true, this);
}
public RotationHelper getRotationHelper() {
@@ -2598,9 +2597,6 @@
mPendingActivityResult = null;
}
- ItemInstallQueue.INSTANCE.get(this)
- .resumeModelPush(FLAG_LOADER_RUNNING);
-
int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty()
? pagesBoundFirst.getArray().get(0) : PagedView.INVALID_PAGE;
// When undoing the removal of the last item on a page, return to that page.
@@ -2677,7 +2673,7 @@
@Override
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
if (!updated.isEmpty()) {
- mWorkspace.updateShortcuts(updated);
+ mWorkspace.updateWorkspaceItems(updated, this);
PopupContainerWithArrow.dismissInvalidPopup(this);
}
}
@@ -2689,7 +2685,7 @@
*/
@Override
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
- mWorkspace.updateRestoreItems(updates);
+ mWorkspace.updateRestoreItems(updates, this);
}
/**
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index eef3980..6966abf 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -144,9 +144,10 @@
enqueueModelUpdateTask(new AddWorkspaceItemsTask(itemList));
}
- public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges) {
+ public ModelWriter getWriter(boolean hasVerticalHotseat, boolean verifyChanges,
+ @Nullable Callbacks owner) {
return new ModelWriter(mApp.getContext(), this, mBgDataModel,
- hasVerticalHotseat, verifyChanges);
+ hasVerticalHotseat, verifyChanges, owner);
}
@Override
@@ -329,7 +330,7 @@
public boolean addCallbacksAndLoad(Callbacks callbacks) {
synchronized (mLock) {
addCallbacks(callbacks);
- return startLoader();
+ return startLoader(new Callbacks[] { callbacks });
}
}
@@ -349,26 +350,32 @@
* @return true if the page could be bound synchronously.
*/
public boolean startLoader() {
+ return startLoader(new Callbacks[0]);
+ }
+
+ private boolean startLoader(Callbacks[] newCallbacks) {
// Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
ItemInstallQueue.INSTANCE.get(mApp.getContext())
.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
synchronized (mLock) {
- // Don't bother to start the thread if we know it's not going to do anything
- final Callbacks[] callbacksList = getCallbacks();
+ // If there is already one running, tell it to stop.
+ boolean wasRunning = stopLoader();
+ boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
+ boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
+ final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;
+
if (callbacksList.length > 0) {
// Clear any pending bind-runnables from the synchronized load process.
for (Callbacks cb : callbacksList) {
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
- // If there is already one running, tell it to stop.
- stopLoader();
LoaderResults loaderResults = new LoaderResults(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
- if (mModelLoaded && !mIsLoaderTaskRunning) {
+ if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
- loaderResults.bindWorkspace();
+ loaderResults.bindWorkspace(bindAllCallbacks);
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
loaderResults.bindAllApps();
@@ -387,7 +394,7 @@
* If there is already a loader task running, tell it to stop.
* @return true if an existing loader was stopped.
*/
- public boolean stopLoader() {
+ private boolean stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
mLoaderTask = null;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5689394..a585be6 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -83,7 +83,6 @@
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.DragPreviewProvider;
-import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.logger.LauncherAtom;
@@ -105,6 +104,7 @@
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
import com.android.launcher3.util.OverlayEdgeEffect;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.RunnableList;
@@ -123,7 +123,6 @@
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -136,7 +135,7 @@
public class Workspace extends PagedView<WorkspacePageIndicator>
implements DropTarget, DragSource, View.OnTouchListener,
DragController.DragListener, Insettable, StateHandler<LauncherState>,
- WorkspaceLayoutManager {
+ WorkspaceLayoutManager, LauncherBindableItemsContainer {
/** The value that {@link #mTransitionProgress} must be greater than for
* {@link #transitionStateShouldAllowDrop()} to return true. */
@@ -3009,9 +3008,9 @@
* @param user The user of the app to match.
*/
public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
- final Workspace.ItemOperator preferredItem = (ItemInfo info, View view) ->
+ final ItemOperator preferredItem = (ItemInfo info, View view) ->
info != null && info.id == preferredItemId;
- final Workspace.ItemOperator preferredItemInFolder = (info, view) -> {
+ final ItemOperator preferredItemInFolder = (info, view) -> {
if (info instanceof FolderInfo) {
FolderInfo folderInfo = (FolderInfo) info;
for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
@@ -3022,14 +3021,14 @@
}
return false;
};
- final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
+ final ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
info != null
&& info.itemType == ITEM_TYPE_APPLICATION
&& info.user.equals(user)
&& info.getTargetComponent() != null
&& TextUtils.equals(info.getTargetComponent().getPackageName(),
packageName);
- final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
+ final ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
if (info instanceof FolderInfo) {
FolderInfo folderInfo = (FolderInfo) info;
for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
@@ -3148,22 +3147,7 @@
stripEmptyScreens();
}
- public interface ItemOperator {
- /**
- * Process the next itemInfo, possibly with side-effect on the next item.
- *
- * @param info info for the shortcut
- * @param view view for the shortcut
- * @return true if done, false to continue the map
- */
- boolean evaluate(ItemInfo info, View view);
- }
-
- /**
- * Map the operator over the shortcuts and widgets, return the first-non-null value.
- *
- * @param op the operator to map over the shortcuts
- */
+ @Override
public void mapOverItems(ItemOperator op) {
for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
if (mapOverCellLayout(layout, op) != null) {
@@ -3189,31 +3173,6 @@
return null;
}
- void updateShortcuts(List<WorkspaceItemInfo> shortcuts) {
- final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
- ItemOperator op = (info, v) -> {
- if (v instanceof BubbleTextView && updates.contains(info)) {
- WorkspaceItemInfo si = (WorkspaceItemInfo) info;
- BubbleTextView shortcut = (BubbleTextView) v;
- Drawable oldIcon = shortcut.getIcon();
- boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
- && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
- } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- }
-
- // Iterate all items
- return false;
- };
-
- mapOverItems(op);
- Folder openFolder = Folder.getOpen(mLauncher);
- if (openFolder != null) {
- openFolder.iterateOverItems(op);
- }
- }
-
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
@@ -3253,28 +3212,6 @@
removeItemsByMatcher(matcher);
}
- public void updateRestoreItems(final HashSet<ItemInfo> updates) {
- ItemOperator op = (info, v) -> {
- if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
- && updates.contains(info)) {
- ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */);
- } else if (v instanceof PendingAppWidgetHostView
- && info instanceof LauncherAppWidgetInfo
- && updates.contains(info)) {
- ((PendingAppWidgetHostView) v).applyState();
- } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
- ((FolderIcon) v).updatePreviewItems(updates::contains);
- }
- // process all the shortcuts
- return false;
- };
- mapOverItems(op);
- Folder folder = Folder.getOpen(mLauncher);
- if (folder != null) {
- folder.iterateOverItems(op);
- }
- }
-
public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
if (!changedInfo.isEmpty()) {
DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 22bb56c..7187188 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -75,7 +75,6 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
import com.android.launcher3.accessibility.FolderAccessibilityHelper;
import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
@@ -94,6 +93,7 @@
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -1196,8 +1196,7 @@
}
void replaceFolderWithFinalItem() {
- mLauncherDelegate.replaceFolderWithFinalItem(this);
- mDestroyed = true;
+ mDestroyed = mLauncherDelegate.replaceFolderWithFinalItem(this);
}
public boolean isDestroyed() {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index d076be6..f005b61 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -679,6 +679,7 @@
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
+ updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
boolean isDotted = mDotInfo.hasDot();
@@ -690,6 +691,7 @@
@Override
public void onRemove(List<WorkspaceItemInfo> items) {
+ updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
boolean isDotted = mDotInfo.hasDot();
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index 3d2884a..65991e4 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -41,12 +41,12 @@
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace.ItemOperator;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
import com.android.launcher3.touch.ItemClickHandler;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.ViewCache;
import com.android.launcher3.views.ActivityContext;
diff --git a/src/com/android/launcher3/folder/LauncherDelegate.java b/src/com/android/launcher3/folder/LauncherDelegate.java
index f7d8e8c..e599e8c 100644
--- a/src/com/android/launcher3/folder/LauncherDelegate.java
+++ b/src/com/android/launcher3/folder/LauncherDelegate.java
@@ -93,7 +93,7 @@
}
}
- void replaceFolderWithFinalItem(Folder folder) {
+ boolean replaceFolderWithFinalItem(Folder folder) {
// Add the last remaining child to the workspace in place of the folder
Runnable onCompleteRunnable = new Runnable() {
@Override
@@ -147,6 +147,7 @@
} else {
onCompleteRunnable.run();
}
+ return true;
}
@@ -191,7 +192,7 @@
ModelWriter getModelWriter() {
if (mWriter == null) {
mWriter = LauncherAppState.getInstance((Context) mContext).getModel()
- .getWriter(false, false);
+ .getWriter(false, false, null);
}
return mWriter;
}
@@ -205,7 +206,9 @@
}
@Override
- void replaceFolderWithFinalItem(Folder folder) { }
+ boolean replaceFolderWithFinalItem(Folder folder) {
+ return false;
+ }
@Override
boolean interceptOutsideTouch(MotionEvent ev, BaseDragLayer dl, Folder folder) {
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index 30755e3..c202d8d 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model;
+import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -73,7 +74,7 @@
/**
* Binds all loaded data to actual views on the main thread.
*/
- public void bindWorkspace() {
+ public void bindWorkspace(boolean incrementBindId) {
// Save a copy of all the bg-thread collections
ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
@@ -85,7 +86,9 @@
appWidgets.addAll(mBgDataModel.appWidgets);
orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
mBgDataModel.extraItems.forEach(extraItems::add);
- mBgDataModel.lastBindId++;
+ if (incrementBindId) {
+ mBgDataModel.lastBindId++;
+ }
mMyBindingId = mBgDataModel.lastBindId;
}
@@ -217,7 +220,11 @@
bindAppWidgets(otherAppWidgets, pendingExecutor);
executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), pendingExecutor);
pendingExecutor.execute(
- () -> MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT));
+ () -> {
+ MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ ItemInstallQueue.INSTANCE.get(mApp.getContext())
+ .resumeModelPush(FLAG_LOADER_RUNNING);
+ });
executeCallbacksTask(
c -> {
diff --git a/src/com/android/launcher3/model/BaseModelUpdateTask.java b/src/com/android/launcher3/model/BaseModelUpdateTask.java
index ad553d5..a3a4717 100644
--- a/src/com/android/launcher3/model/BaseModelUpdateTask.java
+++ b/src/com/android/launcher3/model/BaseModelUpdateTask.java
@@ -90,7 +90,7 @@
public ModelWriter getModelWriter() {
// 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 */);
+ return mModel.getWriter(false /* hasVerticalHotseat */, false /* verifyChanges */, null);
}
public void bindUpdatedWorkspaceItems(List<WorkspaceItemInfo> allUpdates) {
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index ba825ca..dd4c3c3 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -477,6 +477,11 @@
ArrayList<ItemInfo> addNotAnimated, ArrayList<ItemInfo> addAnimated) { }
/**
+ * Called when some persistent property of an item is modified
+ */
+ default void bindItemsModified(List<ItemInfo> items) { }
+
+ /**
* Binds updated incremental download progress
*/
default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index d5b5452..31ca6e7 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -210,7 +210,7 @@
}
verifyNotStopped();
- mResults.bindWorkspace();
+ mResults.bindWorkspace(true /* incrementBindId */);
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 2b93118..55edfd4 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -23,12 +23,13 @@
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 androidx.annotation.Nullable;
+
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherModel.CallbackTask;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -36,19 +37,22 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.logging.FileLog;
+import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.Executors;
import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.widget.LauncherAppWidgetHost;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
-import java.util.concurrent.Executor;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
@@ -63,7 +67,10 @@
private final Context mContext;
private final LauncherModel mModel;
private final BgDataModel mBgDataModel;
- private final Handler mUiHandler;
+ private final LooperExecutor mUiExecutor;
+
+ @Nullable
+ private final Callbacks mOwner;
private final boolean mHasVerticalHotseat;
private final boolean mVerifyChanges;
@@ -73,13 +80,15 @@
private boolean mPreparingToUndo;
public ModelWriter(Context context, LauncherModel model, BgDataModel dataModel,
- boolean hasVerticalHotseat, boolean verifyChanges) {
+ boolean hasVerticalHotseat, boolean verifyChanges,
+ @Nullable Callbacks owner) {
mContext = context;
mModel = model;
mBgDataModel = dataModel;
mHasVerticalHotseat = hasVerticalHotseat;
mVerifyChanges = verifyChanges;
- mUiHandler = new Handler(Looper.getMainLooper());
+ mOwner = owner;
+ mUiExecutor = Executors.MAIN_EXECUTOR;
}
private void updateItemInfoProps(
@@ -162,6 +171,8 @@
public void moveItemInDatabase(final ItemInfo item,
int container, int screenId, int cellX, int cellY) {
updateItemInfoProps(item, container, screenId, cellX, cellY);
+ notifyItemModified(item);
+
enqueueDeleteRunnable(new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
@@ -178,6 +189,7 @@
public void moveItemsInDatabase(final ArrayList<ItemInfo> items, int container, int screen) {
ArrayList<ContentValues> contentValues = new ArrayList<>();
int count = items.size();
+ notifyOtherCallbacks(c -> c.bindItemsModified(items));
for (int i = 0; i < count; i++) {
ItemInfo item = items.get(i);
@@ -203,8 +215,9 @@
updateItemInfoProps(item, container, screenId, cellX, cellY);
item.spanX = spanX;
item.spanY = spanY;
+ notifyItemModified(item);
- ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () ->
+ MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () ->
new ContentWriter(mContext)
.put(Favorites.CONTAINER, item.container)
.put(Favorites.CELLX, item.cellX)
@@ -219,13 +232,18 @@
* Update an item to the database in a specified container.
*/
public void updateItemInDatabase(ItemInfo item) {
- ((Executor) MODEL_EXECUTOR).execute(new UpdateItemRunnable(item, () -> {
+ notifyItemModified(item);
+ MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> {
ContentWriter writer = new ContentWriter(mContext);
item.onAddToDatabase(writer);
return writer;
}));
}
+ private void notifyItemModified(ItemInfo item) {
+ notifyOtherCallbacks(c -> c.bindItemsModified(Collections.singletonList(item)));
+ }
+
/**
* Add an item to the database in a specified container. Sets the container, screen, cellX and
* cellY fields of the item. Also assigns an ID to the item.
@@ -236,10 +254,11 @@
final ContentResolver cr = mContext.getContentResolver();
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
+ notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false));
ModelVerifier verifier = new ModelVerifier();
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
- ((Executor) MODEL_EXECUTOR).execute(() -> {
+ MODEL_EXECUTOR.execute(() -> {
// Write the item on background thread, as some properties might have been updated in
// the background.
final ContentWriter writer = new ContentWriter(mContext);
@@ -281,6 +300,7 @@
(item) -> item.getTargetComponent() == null ? ""
: item.getTargetComponent().getPackageName()).collect(
Collectors.joining(",")), new Exception());
+ notifyDelete(items);
enqueueDeleteRunnable(() -> {
for (ItemInfo item : items) {
final Uri uri = Favorites.getContentUri(item.id);
@@ -297,6 +317,7 @@
*/
public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
ModelVerifier verifier = new ModelVerifier();
+ notifyDelete(Collections.singleton(info));
enqueueDeleteRunnable(() -> {
ContentResolver cr = mContext.getContentResolver();
@@ -315,6 +336,7 @@
* Deletes the widget info and the widget id.
*/
public void deleteWidgetInfo(final LauncherAppWidgetInfo info, LauncherAppWidgetHost host) {
+ notifyDelete(Collections.singleton(info));
if (host != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
// Deleting an app widget ID is a void call but writes to disk before returning
// to the caller...
@@ -323,6 +345,10 @@
deleteItemFromDatabase(info);
}
+ private void notifyDelete(Collection<? extends ItemInfo> items) {
+ notifyOtherCallbacks(c -> c.bindWorkspaceComponentsRemoved(ItemInfoMatcher.ofItems(items)));
+ }
+
/**
* Delete operations tracked using {@link #enqueueDeleteRunnable} will only be called
* if {@link #commitDelete} is called. Note that one of {@link #commitDelete()} or
@@ -348,14 +374,14 @@
if (mPreparingToUndo) {
mDeleteRunnables.add(r);
} else {
- ((Executor) MODEL_EXECUTOR).execute(r);
+ MODEL_EXECUTOR.execute(r);
}
}
public void commitDelete() {
mPreparingToUndo = false;
for (Runnable runnable : mDeleteRunnables) {
- ((Executor) MODEL_EXECUTOR).execute(runnable);
+ MODEL_EXECUTOR.execute(runnable);
}
mDeleteRunnables.clear();
}
@@ -371,6 +397,20 @@
mModel.forceReload();
}
+ private void notifyOtherCallbacks(CallbackTask task) {
+ if (mOwner == null) {
+ // If the call is happening from a model, it will take care of updating the callbacks
+ return;
+ }
+ mUiExecutor.execute(() -> {
+ for (Callbacks c : mModel.getCallbacks()) {
+ if (c != mOwner) {
+ task.execute(c);
+ }
+ }
+ });
+ }
+
private class UpdateItemRunnable extends UpdateItemBaseRunnable {
private final ItemInfo mItem;
private final Supplier<ContentWriter> mWriter;
@@ -491,7 +531,7 @@
int executeId = mBgDataModel.lastBindId;
- mUiHandler.post(() -> {
+ mUiExecutor.post(() -> {
int currentId = mBgDataModel.lastBindId;
if (currentId > executeId) {
// Model was already bound after job was executed.
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index 354609d..e8ba28f 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -23,6 +23,7 @@
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.shortcuts.ShortcutKey;
+import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@@ -89,4 +90,13 @@
static ItemInfoMatcher ofItemIds(IntSet ids) {
return (info, cn) -> ids.contains(info.id);
}
+
+ /**
+ * Returns a matcher for items with provided items
+ */
+ static ItemInfoMatcher ofItems(Collection<? extends ItemInfo> items) {
+ IntSet ids = new IntSet();
+ items.forEach(item -> ids.add(item.id));
+ return ofItemIds(ids);
+ }
}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
new file mode 100644
index 0000000..a4cb30a
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 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.util;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.model.data.FolderInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
+import com.android.launcher3.views.ActivityContext;
+import com.android.launcher3.widget.PendingAppWidgetHostView;
+
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Interface representing a container which can bind Launcher items with some utility methods
+ */
+public interface LauncherBindableItemsContainer {
+
+ /**
+ * Called to update workspace items as a result of
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
+ */
+ default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
+ final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
+ ItemOperator op = (info, v) -> {
+ if (v instanceof BubbleTextView && updates.contains(info)) {
+ WorkspaceItemInfo si = (WorkspaceItemInfo) info;
+ BubbleTextView shortcut = (BubbleTextView) v;
+ Drawable oldIcon = shortcut.getIcon();
+ boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
+ && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
+ shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
+ } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
+ }
+
+ // Iterate all items
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder openFolder = Folder.getOpen(context);
+ if (openFolder != null) {
+ openFolder.iterateOverItems(op);
+ }
+ }
+
+ /**
+ * Called to update restored items as a result of
+ * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
+ */
+ default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
+ ItemOperator op = (info, v) -> {
+ if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
+ && updates.contains(info)) {
+ ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */);
+ } else if (v instanceof PendingAppWidgetHostView
+ && info instanceof LauncherAppWidgetInfo
+ && updates.contains(info)) {
+ ((PendingAppWidgetHostView) v).applyState();
+ } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
+ ((FolderIcon) v).updatePreviewItems(updates::contains);
+ }
+ // process all the shortcuts
+ return false;
+ };
+
+ mapOverItems(op);
+ Folder folder = Folder.getOpen(context);
+ if (folder != null) {
+ folder.iterateOverItems(op);
+ }
+ }
+
+ /**
+ * Map the operator over the shortcuts and widgets.
+ *
+ * @param op the operator to map over the shortcuts
+ */
+ void mapOverItems(ItemOperator op);
+
+ interface ItemOperator {
+ /**
+ * Process the next itemInfo, possibly with side-effect on the next item.
+ *
+ * @param info info for the shortcut
+ * @param view view for the shortcut
+ * @return true if done, false to continue the map
+ */
+ boolean evaluate(ItemInfo info, View view);
+ }
+}