Updating ItemInfoMatcher to work with java streams
Adding support for bulk removing items from a folder icon.
This fixes workspace item removal when a folder gets replaced
to an icon during the delete operation.
- Lets say user has a folder with the same app twice.
- User disables that app.
- Launcher removes all shorcuts of that app
However, because we call "replaceFolderWithFinalItem" during
this removal, we end up creating a new shortcut that does
not get tracked by the removal, so the user is left with
an enabled icon of the disabled app.
Bug: 162378169
Test: manual test, repo steps in bug
Change-Id: Iaf6550894c156b3b5ec2a5aa58bab76a4a28819e
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 65eba20..8a45c81 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -111,9 +111,11 @@
import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlay;
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;
/**
* The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -3002,38 +3004,27 @@
* shortcuts are not removed.
*/
public void removeItemsByMatcher(final ItemInfoMatcher matcher) {
- for (final CellLayout layoutParent: getWorkspaceAndHotseatCellLayouts()) {
- final ViewGroup layout = layoutParent.getShortcutsAndWidgets();
+ for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
+ ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
+ // Iterate in reverse order as we are removing items
+ for (int i = container.getChildCount() - 1; i >= 0; i--) {
+ View child = container.getChildAt(i);
+ ItemInfo info = (ItemInfo) child.getTag();
- IntSparseArrayMap<View> idToViewMap = new IntSparseArrayMap<>();
- ArrayList<ItemInfo> items = new ArrayList<>();
- for (int j = 0; j < layout.getChildCount(); j++) {
- final View view = layout.getChildAt(j);
- if (view.getTag() instanceof ItemInfo) {
- ItemInfo item = (ItemInfo) view.getTag();
- items.add(item);
- idToViewMap.put(item.id, view);
- }
- }
-
- for (ItemInfo itemToRemove : matcher.filterItemInfos(items)) {
- View child = idToViewMap.get(itemToRemove.id);
-
- if (child != null) {
- // Note: We can not remove the view directly from CellLayoutChildren as this
- // does not re-mark the spaces as unoccupied.
- layoutParent.removeViewInLayout(child);
+ if (matcher.matchesInfo(info)) {
+ layout.removeViewInLayout(child);
if (child instanceof DropTarget) {
mDragController.removeDropTarget((DropTarget) child);
}
- } else if (itemToRemove.container >= 0) {
- // The item may belong to a folder.
- View parent = idToViewMap.get(itemToRemove.container);
- if (parent instanceof FolderIcon) {
- FolderInfo folderInfo = (FolderInfo) parent.getTag();
- folderInfo.remove((WorkspaceItemInfo) itemToRemove, false);
- if (((FolderIcon) parent).getFolder().isOpen()) {
- ((FolderIcon) parent).getFolder().close(false /* animate */);
+ } else if (child instanceof FolderIcon) {
+ FolderInfo folderInfo = (FolderInfo) info;
+ List<WorkspaceItemInfo> matches = folderInfo.contents.stream()
+ .filter(matcher::matchesInfo)
+ .collect(Collectors.toList());
+ if (!matches.isEmpty()) {
+ folderInfo.removeAll(matches, false);
+ if (((FolderIcon) child).getFolder().isOpen()) {
+ ((FolderIcon) child).getFolder().close(false /* animate */);
}
}
}
@@ -3143,9 +3134,8 @@
}
public void removeAbandonedPromise(String packageName, UserHandle user) {
- HashSet<String> packages = new HashSet<>(1);
- packages.add(packageName);
- ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(packages, user);
+ ItemInfoMatcher matcher = ItemInfoMatcher.ofPackages(
+ Collections.singleton(packageName), user);
mLauncher.getModelWriter().deleteItemsFromDatabase(matcher);
removeItemsByMatcher(matcher);
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 63fa391..61938d1 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -1394,10 +1394,10 @@
mItemsInvalidated = true;
}
- public void onRemove(WorkspaceItemInfo item) {
+ @Override
+ public void onRemove(List<WorkspaceItemInfo> items) {
mItemsInvalidated = true;
- View v = getViewForInfo(item);
- mContent.removeItem(v);
+ items.stream().map(this::getViewForInfo).forEach(mContent::removeItem);
if (mState == STATE_ANIMATING) {
mRearrangeOnClose = true;
} else {
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 3296eed..fe310f6 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -695,9 +695,9 @@
}
@Override
- public void onRemove(WorkspaceItemInfo item) {
+ public void onRemove(List<WorkspaceItemInfo> items) {
boolean wasDotted = mDotInfo.hasDot();
- mDotInfo.subtractDotInfo(mActivity.getDotInfoForItem(item));
+ items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
boolean isDotted = mDotInfo.hasDot();
updateDotScale(wasDotted, isDotted);
setContentDescription(getAccessiblityTitle(mInfo.title));
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 2c99df7..f7b43d6 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -51,6 +51,7 @@
import java.util.concurrent.Executor;
import java.util.function.Supplier;
import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
/**
* Class for handling model updates.
@@ -259,7 +260,9 @@
* Removes all the items from the database matching {@param matcher}.
*/
public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
- deleteItemsFromDatabase(matcher.filterItemInfos(mBgDataModel.itemsIdMap));
+ deleteItemsFromDatabase(StreamSupport.stream(mBgDataModel.itemsIdMap.spliterator(), false)
+ .filter(matcher::matchesInfo)
+ .collect(Collectors.toList()));
}
/**
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index 06a2c92..cc783f7 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -40,6 +40,8 @@
import com.android.launcher3.util.ContentWriter;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.OptionalInt;
import java.util.stream.IntStream;
@@ -137,9 +139,16 @@
* @param item
*/
public void remove(WorkspaceItemInfo item, boolean animate) {
- contents.remove(item);
+ removeAll(Collections.singletonList(item), animate);
+ }
+
+ /**
+ * Remove all matching app or shortcut. Does not change the DB.
+ */
+ public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
+ contents.removeAll(items);
for (int i = 0; i < mListeners.size(); i++) {
- mListeners.get(i).onRemove(item);
+ mListeners.get(i).onRemove(items);
}
itemsChanged(animate);
}
@@ -166,9 +175,9 @@
}
public interface FolderListener {
- public void onAdd(WorkspaceItemInfo item, int rank);
- public void onRemove(WorkspaceItemInfo item);
- public void onItemsChanged(boolean animate);
+ void onAdd(WorkspaceItemInfo item, int rank);
+ void onRemove(List<WorkspaceItemInfo> item);
+ void onItemsChanged(boolean animate);
}
public boolean hasOption(int optionFlag) {
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index d26bb57..354609d 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -20,10 +20,7 @@
import android.os.UserHandle;
import com.android.launcher3.LauncherSettings.Favorites;
-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.shortcuts.ShortcutKey;
import java.util.HashSet;
@@ -37,34 +34,15 @@
boolean matches(ItemInfo info, ComponentName cn);
/**
- * Filters {@param infos} to those satisfying the {@link #matches(ItemInfo, ComponentName)}.
+ * Returns true if the itemInfo matches this check
*/
- default HashSet<ItemInfo> filterItemInfos(Iterable<ItemInfo> infos) {
- HashSet<ItemInfo> filtered = new HashSet<>();
- for (ItemInfo i : infos) {
- if (i instanceof WorkspaceItemInfo) {
- WorkspaceItemInfo info = (WorkspaceItemInfo) i;
- ComponentName cn = info.getTargetComponent();
- if (cn != null && matches(info, cn)) {
- filtered.add(info);
- }
- } else if (i instanceof FolderInfo) {
- FolderInfo info = (FolderInfo) i;
- for (WorkspaceItemInfo s : info.contents) {
- ComponentName cn = s.getTargetComponent();
- if (cn != null && matches(s, cn)) {
- filtered.add(s);
- }
- }
- } else if (i instanceof LauncherAppWidgetInfo) {
- LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) i;
- ComponentName cn = info.providerName;
- if (cn != null && matches(info, cn)) {
- filtered.add(info);
- }
- }
+ default boolean matchesInfo(ItemInfo info) {
+ if (info != null) {
+ ComponentName cn = info.getTargetComponent();
+ return cn != null && matches(info, cn);
+ } else {
+ return false;
}
- return filtered;
}
/**
@@ -96,7 +74,7 @@
return (info, cn) -> components.contains(cn) && info.user.equals(user);
}
- static ItemInfoMatcher ofPackages(HashSet<String> packageNames, UserHandle user) {
+ static ItemInfoMatcher ofPackages(Set<String> packageNames, UserHandle user) {
return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user);
}