From bfc0c38157736282129d548e79e16c32793ef87a Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Wed, 27 Jan 2021 15:16:59 -0800 Subject: [PATCH] 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 --- src/com/android/launcher3/Workspace.java | 52 ++++++++----------- src/com/android/launcher3/folder/Folder.java | 6 +-- .../android/launcher3/folder/FolderIcon.java | 4 +- .../android/launcher3/model/ModelWriter.java | 5 +- .../launcher3/model/data/FolderInfo.java | 19 +++++-- .../launcher3/util/ItemInfoMatcher.java | 38 +++----------- 6 files changed, 52 insertions(+), 72 deletions(-) diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 65eba20672..8a45c81d97 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -111,9 +111,11 @@ import com.android.launcher3.widget.WidgetManagerHelper; 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 @@ public class Workspace extends PagedView * 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 idToViewMap = new IntSparseArrayMap<>(); - ArrayList 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 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 class Workspace extends PagedView } public void removeAbandonedPromise(String packageName, UserHandle user) { - HashSet 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 63fa391d43..61938d1719 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -1394,10 +1394,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mItemsInvalidated = true; } - public void onRemove(WorkspaceItemInfo item) { + @Override + public void onRemove(List 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 3296eed5ca..fe310f626c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -695,9 +695,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } @Override - public void onRemove(WorkspaceItemInfo item) { + public void onRemove(List 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 2c99df7a81..f7b43d6bec 100644 --- a/src/com/android/launcher3/model/ModelWriter.java +++ b/src/com/android/launcher3/model/ModelWriter.java @@ -51,6 +51,7 @@ import java.util.List; 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 @@ public class ModelWriter { * 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 06a2c92dab..cc783f7a5e 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.model.ModelWriter; 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 @@ public class FolderInfo extends ItemInfo { * @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 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 class FolderInfo extends ItemInfo { } 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 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 d26bb5779e..354609d463 100644 --- a/src/com/android/launcher3/util/ItemInfoMatcher.java +++ b/src/com/android/launcher3/util/ItemInfoMatcher.java @@ -20,10 +20,7 @@ import android.content.ComponentName; 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 @@ public interface ItemInfoMatcher { 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 filterItemInfos(Iterable infos) { - HashSet 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 @@ public interface ItemInfoMatcher { return (info, cn) -> components.contains(cn) && info.user.equals(user); } - static ItemInfoMatcher ofPackages(HashSet packageNames, UserHandle user) { + static ItemInfoMatcher ofPackages(Set packageNames, UserHandle user) { return (info, cn) -> packageNames.contains(cn.getPackageName()) && info.user.equals(user); }