From ac6f69f78d4d8d58b12cc58e2074181aca28a1ba Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 16 Aug 2019 11:59:55 -0700 Subject: [PATCH] Lazily binding folder pages and icons Folders are bound before they are opened and unbound on close. This allows us to recycle the views in folders Bug: 139051851 Change-Id: Ic1ed3265c0f583af54d73dab6f2751bc95266ea9 --- res/layout/folder_application.xml | 1 + src/com/android/launcher3/FolderInfo.java | 15 -- src/com/android/launcher3/Launcher.java | 5 + src/com/android/launcher3/Workspace.java | 182 +++++++----------- src/com/android/launcher3/folder/Folder.java | 112 ++++++----- .../android/launcher3/folder/FolderIcon.java | 36 ++-- .../launcher3/folder/FolderPagedView.java | 103 ++++++---- .../folder/PreviewItemDrawingParams.java | 7 +- .../launcher3/folder/PreviewItemManager.java | 128 ++++++++---- 9 files changed, 304 insertions(+), 285 deletions(-) diff --git a/res/layout/folder_application.xml b/res/layout/folder_application.xml index c156e113fb..32a5419b8e 100644 --- a/res/layout/folder_application.xml +++ b/res/layout/folder_application.xml @@ -20,4 +20,5 @@ style="@style/BaseIcon" android:textColor="?attr/folderTextColor" android:includeFontPadding="false" + android:hapticFeedbackEnabled="false" launcher:iconDisplay="folder" /> diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java index 67d5ab0876..e2b7b68a97 100644 --- a/src/com/android/launcher3/FolderInfo.java +++ b/src/com/android/launcher3/FolderInfo.java @@ -93,13 +93,6 @@ public class FolderInfo extends ItemInfo { itemsChanged(animate); } - public void setTitle(CharSequence title) { - this.title = title; - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).onTitleChanged(title); - } - } - @Override public void onAddToDatabase(ContentWriter writer) { super.onAddToDatabase(writer); @@ -121,18 +114,10 @@ public class FolderInfo extends ItemInfo { } } - public void prepareAutoUpdate() { - for (int i = 0; i < mListeners.size(); i++) { - mListeners.get(i).prepareAutoUpdate(); - } - } - public interface FolderListener { public void onAdd(WorkspaceItemInfo item, int rank); public void onRemove(WorkspaceItemInfo item); - public void onTitleChanged(CharSequence title); public void onItemsChanged(boolean animate); - public void prepareAutoUpdate(); } public boolean hasOption(int optionFlag) { diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index aa02441ff1..886fd8e638 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2340,6 +2340,11 @@ public class Launcher extends BaseDraggingActivity implements LauncherExterns, // override the previous page so we don't log the page switch. mWorkspace.setCurrentPage(pageBoundFirst, pageBoundFirst /* overridePrevPage */); + // Cache one page worth of icons + getViewCache().setCacheSize(R.layout.folder_application, + mDeviceProfile.inv.numFolderColumns * mDeviceProfile.inv.numFolderRows); + getViewCache().setCacheSize(R.layout.folder_page, 2); + TraceHelper.endSection("finishBindingItems"); } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 1b757d5c0e..56a896627e 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -92,7 +92,6 @@ import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; import com.android.launcher3.userevent.nano.LauncherLogProto.Target; import com.android.launcher3.util.Executors; import com.android.launcher3.util.IntArray; -import com.android.launcher3.util.IntSet; import com.android.launcher3.util.IntSparseArrayMap; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.PackageUserKey; @@ -134,9 +133,6 @@ public class Workspace extends PagedView private static final int DEFAULT_PAGE = 0; - public static final boolean MAP_NO_RECURSE = false; - public static final boolean MAP_RECURSE = true; - private LayoutTransition mLayoutTransition; @Thunk final WallpaperManager mWallpaperManager; @@ -2809,7 +2805,7 @@ public class Workspace extends PagedView * Removes all folder listeners */ public void removeFolderListeners() { - mapOverItems(false, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof FolderIcon) { @@ -2961,7 +2957,7 @@ public class Workspace extends PagedView public View getFirstMatch(final ItemOperator operator) { final View[] value = new View[1]; - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (operator.evaluate(info, v)) { @@ -2984,7 +2980,7 @@ public class Workspace extends PagedView final View[] matches = new View[operators.length]; // For efficiency, the outer loop should be CellLayout. for (CellLayout cellLayout : cellLayouts) { - mapOverCellLayout(MAP_NO_RECURSE, cellLayout, (info, v) -> { + mapOverCellLayout(cellLayout, (info, v) -> { for (int i = 0; i < operators.length; ++i) { if (matches[i] == null && operators[i].evaluate(info, v)) { matches[i] = v; @@ -3009,7 +3005,7 @@ public class Workspace extends PagedView } void clearDropTargets() { - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View v) { if (v instanceof DropTarget) { @@ -3054,10 +3050,12 @@ public class Workspace extends PagedView } else if (itemToRemove.container >= 0) { // The item may belong to a folder. View parent = idToViewMap.get(itemToRemove.container); - if (parent != null) { + if (parent instanceof FolderIcon) { FolderInfo folderInfo = (FolderInfo) parent.getTag(); - folderInfo.prepareAutoUpdate(); folderInfo.remove((WorkspaceItemInfo) itemToRemove, false); + if (((FolderIcon) parent).getFolder().isOpen()) { + ((FolderIcon) parent).getFolder().close(false /* animate */); + } } } } @@ -3081,18 +3079,17 @@ public class Workspace extends PagedView /** * Map the operator over the shortcuts and widgets, return the first-non-null value. * - * @param recurse true: iterate over folder children. false: op get the folders themselves. * @param op the operator to map over the shortcuts */ - public void mapOverItems(boolean recurse, ItemOperator op) { + public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - if (mapOverCellLayout(recurse, layout, op)) { + if (mapOverCellLayout(layout, op)) { return; } } } - private boolean mapOverCellLayout(boolean recurse, CellLayout layout, ItemOperator op) { + private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) { // TODO(b/128460496) Potential race condition where layout is not yet loaded if (layout == null) { return false; @@ -3102,103 +3099,68 @@ public class Workspace extends PagedView final int itemCount = container.getChildCount(); for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { View item = container.getChildAt(itemIdx); - ItemInfo info = (ItemInfo) item.getTag(); - if (recurse && info instanceof FolderInfo && item instanceof FolderIcon) { - FolderIcon folder = (FolderIcon) item; - ArrayList folderChildren = folder.getFolder().getIconsInReadingOrder(); - // map over all the children in the folder - final int childCount = folderChildren.size(); - for (int childIdx = 0; childIdx < childCount; childIdx++) { - View child = folderChildren.get(childIdx); - info = (ItemInfo) child.getTag(); - if (op.evaluate(info, child)) { - return true; - } - } - } else { - if (op.evaluate(info, item)) { - return true; - } + if (op.evaluate((ItemInfo) item.getTag(), item)) { + return true; } } return false; } void updateShortcuts(ArrayList shortcuts) { - int total = shortcuts.size(); - final HashSet updates = new HashSet<>(total); - final IntSet folderIds = new IntSet(); + final HashSet 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); + } - for (int i = 0; i < total; i++) { - WorkspaceItemInfo s = shortcuts.get(i); - updates.add(s); - folderIds.add(s.container); + // Iterate all items + return false; + }; + + mapOverItems(op); + Folder openFolder = Folder.getOpen(mLauncher); + if (openFolder != null) { + openFolder.iterateOverItems(op); } - - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && 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); - } - // process all the shortcuts - return false; - } - }); - - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id)) { - ((FolderInfo) info).itemsChanged(false); - } - // process all the shortcuts - return false; - } - }); } public void updateNotificationDots(Predicate updatedDots) { final PackageUserKey packageUserKey = new PackageUserKey(null, null); - final IntSet folderIds = new IntSet(); - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { - if (!packageUserKey.updateFromItemInfo(info) - || updatedDots.test(packageUserKey)) { - ((BubbleTextView) v).applyDotState(info, true /* animate */); - folderIds.add(info.container); - } - } - // process all the shortcuts - return false; - } - }); + Predicate matcher = info -> !packageUserKey.updateFromItemInfo(info) + || updatedDots.test(packageUserKey); - // Update folder icons - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof FolderInfo && folderIds.contains(info.id) - && v instanceof FolderIcon) { + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) { + if (matcher.test(info)) { + ((BubbleTextView) v).applyDotState(info, true /* animate */); + } + } else if (info instanceof FolderInfo && v instanceof FolderIcon) { + FolderInfo fi = (FolderInfo) info; + if (fi.contents.stream().anyMatch(matcher)) { FolderDotInfo folderDotInfo = new FolderDotInfo(); - for (WorkspaceItemInfo si : ((FolderInfo) info).contents) { + for (WorkspaceItemInfo si : fi.contents) { folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si)); } ((FolderIcon) v).setDotInfo(folderDotInfo); } - // process all the shortcuts - return false; } - }); + + // process all the shortcuts + return false; + }; + + mapOverItems(op); + Folder folder = Folder.getOpen(mLauncher); + if (folder != null) { + folder.iterateOverItems(op); + } } public void removeAbandonedPromise(String packageName, UserHandle user) { @@ -3210,21 +3172,25 @@ public class Workspace extends PagedView } public void updateRestoreItems(final HashSet updates) { - mapOverItems(MAP_RECURSE, new ItemOperator() { - @Override - public boolean evaluate(ItemInfo info, View v) { - if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView - && updates.contains(info)) { - ((BubbleTextView) v).applyPromiseState(false /* promiseStateChanged */); - } else if (v instanceof PendingAppWidgetHostView - && info instanceof LauncherAppWidgetInfo - && updates.contains(info)) { - ((PendingAppWidgetHostView) v).applyState(); - } - // process all the shortcuts - return false; + ItemOperator op = (info, v) -> { + if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView + && updates.contains(info)) { + ((BubbleTextView) v).applyPromiseState(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 changedInfo) { @@ -3248,7 +3214,7 @@ public class Workspace extends PagedView } else { // widgetRefresh will automatically run when the packages are updated. // For now just update the progress bars - mapOverItems(MAP_NO_RECURSE, new ItemOperator() { + mapOverItems(new ItemOperator() { @Override public boolean evaluate(ItemInfo info, View view) { if (view instanceof PendingAppWidgetHostView @@ -3372,7 +3338,7 @@ public class Workspace extends PagedView mRefreshPending = false; ArrayList views = new ArrayList<>(mInfos.size()); - mapOverItems(MAP_NO_RECURSE, (info, view) -> { + mapOverItems((info, view) -> { if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) { views.add((PendingAppWidgetHostView) view); } diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 28c25cf786..ab308b3b1b 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -170,8 +170,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo private boolean mDeleteFolderOnDropCompleted = false; private boolean mSuppressFolderDeletion = false; private boolean mItemAddedBackToSelfViaIcon = false; - @Thunk float mFolderIconPivotX; - @Thunk float mFolderIconPivotY; private boolean mIsEditingName = false; @ViewDebug.ExportedProperty(category = "launcher") @@ -310,7 +308,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo // Convert to a string here to ensure that no other state associated with the text field // gets saved. String newTitle = mFolderName.getText().toString(); - mInfo.setTitle(newTitle); + mInfo.title = newTitle; + mFolderIcon.onTitleChanged(newTitle); mLauncher.getModelWriter().updateItemInDatabase(mInfo); if (TextUtils.isEmpty(mInfo.title)) { @@ -385,7 +384,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo ArrayList children = info.contents; Collections.sort(children, ITEM_POS_COMPARATOR); updateItemLocationsInDatabaseBatch(); - mContent.bindItems(children); DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); if (lp == null) { @@ -393,13 +391,10 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo lp.customPosition = true; setLayoutParams(lp); } - centerAboutIcon(); - mItemsInvalidated = true; - updateTextViewFocus(); mInfo.addListener(this); - if (TextUtils.isEmpty(mInfo.title)) { + if (!TextUtils.isEmpty(mInfo.title)) { mFolderName.setText(mInfo.title); mFolderName.setHint(null); } else { @@ -408,11 +403,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } // In case any children didn't come across during loading, clean up the folder accordingly - mFolderIcon.post(new Runnable() { - public void run() { - if (getItemCount() <= 1) { - replaceFolderWithFinalItem(); - } + mFolderIcon.post(() -> { + if (getItemCount() <= 1) { + replaceFolderWithFinalItem(); } }); } @@ -472,18 +465,50 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return folderCount >= MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION; } + /** + * Opens the folder as part of a drag operation + */ + public void beginExternalDrag() { + mIsExternalDrag = true; + mDragInProgress = true; + + // Since this folder opened by another controller, it might not get onDrop or + // onDropComplete. Perform cleanup once drag-n-drop ends. + mDragController.addDragListener(this); + + ArrayList items = new ArrayList<>(mInfo.contents); + mEmptyCellRank = items.size(); + items.add(null); // Add an empty spot at the end + + animateOpen(items, mEmptyCellRank / mContent.itemsPerPage()); + } + /** * Opens the user folder described by the specified tag. The opening of the folder * is animated relative to the specified View. If the View is null, no animation * is played. */ public void animateOpen() { + animateOpen(mInfo.contents, 0); + } + + /** + * Opens the user folder described by the specified tag. The opening of the folder + * is animated relative to the specified View. If the View is null, no animation + * is played. + */ + private void animateOpen(List items, int pageNo) { Folder openFolder = getOpen(mLauncher); if (openFolder != null && openFolder != this) { // Close any open folder before opening a folder. openFolder.close(true); } + mContent.bindItems(items); + centerAboutIcon(); + mItemsInvalidated = true; + updateTextViewFocus(); + mIsOpen = true; DragLayer dragLayer = mLauncher.getDragLayer(); @@ -500,10 +525,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } mContent.completePendingPageChanges(); - if (!mDragInProgress) { - // Open on the first page. - mContent.snapToPageImmediately(0); - } + mContent.snapToPageImmediately(pageNo); // This is set to true in close(), but isn't reset to false until onDropCompleted(). This // leads to an inconsistent state if you drag out of the folder and drag back in without @@ -574,16 +596,6 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo mContent.verifyVisibleHighResIcons(mContent.getNextPage()); } - public void beginExternalDrag() { - mEmptyCellRank = mContent.allocateRankForNewItem(); - mIsExternalDrag = true; - mDragInProgress = true; - - // Since this folder opened by another controller, it might not get onDrop or - // onDropComplete. Perform cleanup once drag-n-drop ends. - mDragController.addDragListener(this); - } - @Override protected boolean isOfType(int type) { return (type & TYPE_FOLDER) != 0; @@ -668,6 +680,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } else if (mDragInProgress) { mDeleteFolderOnDropCompleted = true; } + } else if (!mDragInProgress) { + mContent.unbindItems(); } mSuppressFolderDeletion = false; clearDragInfo(); @@ -953,24 +967,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo setPivotX(folderPivotX); setPivotY(folderPivotY); - mFolderIconPivotX = (int) (mFolderIcon.getMeasuredWidth() * - (1.0f * folderPivotX / width)); - mFolderIconPivotY = (int) (mFolderIcon.getMeasuredHeight() * - (1.0f * folderPivotY / height)); - lp.width = width; lp.height = height; lp.x = left; lp.y = top; } - public float getPivotXForIconAnimation() { - return mFolderIconPivotX; - } - public float getPivotYForIconAnimation() { - return mFolderIconPivotY; - } - private int getContentAreaHeight() { DeviceProfile grid = mLauncher.getDeviceProfile(); int maxContentAreaHeight = grid.availableHeightPx @@ -1031,7 +1033,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo } public int getItemCount() { - return mContent.getItemCount(); + return mInfo.contents.size(); } @Thunk void replaceFolderWithFinalItem() { @@ -1039,7 +1041,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo Runnable onCompleteRunnable = new Runnable() { @Override public void run() { - int itemCount = mInfo.contents.size(); + int itemCount = getItemCount(); if (itemCount <= 1) { View newIcon = null; @@ -1116,6 +1118,8 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } }); + } else { + setOnKeyListener(null); } } @@ -1242,9 +1246,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo item.cellY); updateItemLocationsInDatabaseBatch(); - ArrayList items = new ArrayList<>(getIconsInReadingOrder()); - items.add(rank, mContent.createAndAddViewForRank(item, rank)); - mContent.arrangeChildren(items); + if (mContent.areViewsBound()) { + mContent.createAndAddViewForRank(item, rank); + } mItemsInvalidated = true; } @@ -1275,12 +1279,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo updateTextViewFocus(); } - @Override - public void prepareAutoUpdate() { - close(false); - } - - public void onTitleChanged(CharSequence title) { + /** + * Utility methods to iterate over items of the view + */ + public void iterateOverItems(ItemOperator op) { + mContent.iterateOverItems(op); } /** @@ -1289,14 +1292,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo public ArrayList getIconsInReadingOrder() { if (mItemsInvalidated) { mItemsInReadingOrder.clear(); - mContent.iterateOverItems(new ItemOperator() { - - @Override - public boolean evaluate(ItemInfo info, View view) { - mItemsInReadingOrder.add(view); - return false; - } - }); + mContent.iterateOverItems((i, v) -> !mItemsInReadingOrder.add(v)); mItemsInvalidated = false; } return mItemsInReadingOrder; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 686684dce2..8ce318b45a 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -69,6 +69,7 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; /** * An icon that can appear on in the workspace representing an {@link Folder}. @@ -99,7 +100,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { ClippedFolderIconLayoutRule mPreviewLayoutRule; private PreviewItemManager mPreviewItemManager; private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0); - private List mCurrentPreviewItems = new ArrayList<>(); + private List mCurrentPreviewItems = new ArrayList<>(); boolean mAnimating = false; @@ -255,7 +256,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { OnAlarmListener mOnOpenListener = new OnAlarmListener() { public void onAlarm(Alarm alarm) { mFolder.beginExternalDrag(); - mFolder.animateOpen(); } }; @@ -321,18 +321,17 @@ public class FolderIcon extends FrameLayout implements FolderListener { int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1); boolean itemAdded = false; if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) { - List oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); + List oldPreviewItems = new ArrayList<>(mCurrentPreviewItems); mInfo.add(item, index, false); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0)); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); if (!oldPreviewItems.equals(mCurrentPreviewItems)) { - for (int i = 0; i < mCurrentPreviewItems.size(); ++i) { - if (mCurrentPreviewItems.get(i).getTag().equals(item)) { - // If the item dropped is going to be in the preview, we update the - // index here to reflect its position in the preview. - index = i; - } + int newIndex = mCurrentPreviewItems.indexOf(item); + if (newIndex >= 0) { + // If the item dropped is going to be in the preview, we update the + // index here to reflect its position in the preview. + index = newIndex; } mPreviewItemManager.hidePreviewItem(index, true); @@ -531,11 +530,10 @@ public class FolderIcon extends FrameLayout implements FolderListener { } /** - * Returns the list of "preview items" on {@param page}. + * Returns the list of items which should be visible in the preview */ - public List getPreviewIconsOnPage(int page) { - return mPreviewVerifier.setFolderInfo(mFolder.mInfo) - .previewItemsForPage(page, mFolder.getIconsInReadingOrder()); + public List getPreviewItemsOnPage(int page) { + return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents); } @Override @@ -553,11 +551,14 @@ public class FolderIcon extends FrameLayout implements FolderListener { private void updatePreviewItems(boolean animate) { mPreviewItemManager.updatePreviewItems(animate); mCurrentPreviewItems.clear(); - mCurrentPreviewItems.addAll(getPreviewIconsOnPage(0)); + mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0)); } - @Override - public void prepareAutoUpdate() { + /** + * Updates the preview items which match the provided condition + */ + public void updatePreviewItems(Predicate itemCheck) { + mPreviewItemManager.updatePreviewItems(itemCheck); } @Override @@ -580,7 +581,6 @@ public class FolderIcon extends FrameLayout implements FolderListener { requestLayout(); } - @Override public void onTitleChanged(CharSequence title) { mFolderName.setText(title); setContentDescription(getContext().getString(R.string.folder_name_format, title)); diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java index 3e00cae9c9..54b363e909 100644 --- a/src/com/android/launcher3/folder/FolderPagedView.java +++ b/src/com/android/launcher3/folder/FolderPagedView.java @@ -24,10 +24,10 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.Log; import android.view.Gravity; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewDebug; +import com.android.launcher3.BaseActivity; import com.android.launcher3.BubbleTextView; import com.android.launcher3.CellLayout; import com.android.launcher3.DeviceProfile; @@ -46,11 +46,14 @@ import com.android.launcher3.keyboard.ViewGroupFocusHelper; import com.android.launcher3.pageindicators.PageIndicatorDots; import com.android.launcher3.touch.ItemClickHandler; import com.android.launcher3.util.Thunk; +import com.android.launcher3.util.ViewCache; import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.function.ToIntFunction; +import java.util.stream.Collectors; public class FolderPagedView extends PagedView { @@ -69,12 +72,12 @@ public class FolderPagedView extends PagedView { public final boolean mIsRtl; - private final LayoutInflater mInflater; private final ViewGroupFocusHelper mFocusIndicatorHelper; @Thunk final ArrayMap mPendingAnimations = new ArrayMap<>(); private final FolderGridOrganizer mOrganizer; + private final ViewCache mViewCache; private int mAllocatedContentSize; @ViewDebug.ExportedProperty(category = "launcher") @@ -84,17 +87,20 @@ public class FolderPagedView extends PagedView { private Folder mFolder; + // If the views are attached to the folder or not. A folder should be bound when its + // animating or is open. + private boolean mViewsBound = false; + public FolderPagedView(Context context, AttributeSet attrs) { super(context, attrs); InvariantDeviceProfile profile = LauncherAppState.getIDP(context); mOrganizer = new FolderGridOrganizer(profile); - mInflater = LayoutInflater.from(context); - mIsRtl = Utilities.isRtl(getResources()); setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); mFocusIndicatorHelper = new ViewGroupFocusHelper(this); + mViewCache = BaseActivity.fromContext(context).getViewCache(); } public void setFolder(Folder folder) { @@ -127,35 +133,50 @@ public class FolderPagedView extends PagedView { /** * Binds items to the layout. */ - public void bindItems(ArrayList items) { - ArrayList icons = new ArrayList<>(); - for (WorkspaceItemInfo item : items) { - icons.add(createNewView(item)); + public void bindItems(List items) { + if (mViewsBound) { + unbindItems(); } - arrangeChildren(icons); - } - - public void allocateSpaceForRank(int rank) { - ArrayList views = new ArrayList<>(mFolder.getIconsInReadingOrder()); - views.add(rank, null); - arrangeChildren(views); + arrangeChildren(items.stream().map(this::createNewView).collect(Collectors.toList())); + mViewsBound = true; } /** - * Create space for a new item at the end, and returns the rank for that item. - * Also sets the current page to the last page. + * Removes all the icons from the folder */ - public int allocateRankForNewItem() { - int rank = getItemCount(); - allocateSpaceForRank(rank); - setCurrentPage(rank / mOrganizer.getMaxItemsPerPage()); - return rank; + public void unbindItems() { + for (int i = getChildCount() - 1; i >= 0; i--) { + CellLayout page = (CellLayout) getChildAt(i); + ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets(); + for (int j = container.getChildCount() - 1; j >= 0; j--) { + mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j)); + } + page.removeAllViews(); + mViewCache.recycleView(R.layout.folder_page, page); + } + removeAllViews(); + mViewsBound = false; } + /** + * Returns true if the icons are bound to the folder + */ + public boolean areViewsBound() { + return mViewsBound; + } + + /** + * Creates and adds an icon corresponding to the provided rank + * @return the created icon + */ public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) { View icon = createNewView(item); - allocateSpaceForRank(rank); - addViewForRank(icon, item, rank); + if (!mViewsBound) { + return icon; + } + ArrayList views = new ArrayList<>(mFolder.getIconsInReadingOrder()); + views.add(rank, icon); + arrangeChildren(views); return icon; } @@ -173,16 +194,24 @@ public class FolderPagedView extends PagedView { @SuppressLint("InflateParams") public View createNewView(WorkspaceItemInfo item) { - final BubbleTextView textView = (BubbleTextView) mInflater.inflate( - R.layout.folder_application, null, false); + if (item == null) { + return null; + } + final BubbleTextView textView = mViewCache.getView( + R.layout.folder_application, getContext(), null); textView.applyFromWorkspaceItem(item); - textView.setHapticFeedbackEnabled(false); textView.setOnClickListener(ItemClickHandler.INSTANCE); textView.setOnLongClickListener(mFolder); textView.setOnFocusChangeListener(mFocusIndicatorHelper); - - textView.setLayoutParams(new CellLayout.LayoutParams( - item.cellX, item.cellY, item.spanX, item.spanY)); + CellLayout.LayoutParams lp = (CellLayout.LayoutParams) textView.getLayoutParams(); + if (lp == null) { + textView.setLayoutParams(new CellLayout.LayoutParams( + item.cellX, item.cellY, item.spanX, item.spanY)); + } else { + lp.cellX = item.cellX; + lp.cellY = item.cellY; + lp.cellHSpan = lp.cellVSpan = 1; + } return textView; } @@ -197,7 +226,7 @@ public class FolderPagedView extends PagedView { private CellLayout createAndAddNewPage() { DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile(); - CellLayout page = (CellLayout) mInflater.inflate(R.layout.folder_page, this, false); + CellLayout page = mViewCache.getView(R.layout.folder_page, getContext(), this); page.setCellDimensions(grid.folderCellWidthPx, grid.folderCellHeightPx); page.getShortcutsAndWidgets().setMotionEventSplittingEnabled(false); page.setInvertIfRtl(true); @@ -240,7 +269,7 @@ public class FolderPagedView extends PagedView { * @param list the ordered list of children. */ @SuppressLint("RtlHardcoded") - public void arrangeChildren(ArrayList list) { + public void arrangeChildren(List list) { int itemCount = list.size(); ArrayList pages = new ArrayList<>(); for (int i = 0; i < getChildCount(); i++) { @@ -313,16 +342,6 @@ public class FolderPagedView extends PagedView { (getPageAt(0).getDesiredHeight() + getPaddingTop() + getPaddingBottom()) : 0; } - public int getItemCount() { - int lastPageIndex = getChildCount() - 1; - if (lastPageIndex < 0) { - // If there are no pages, nothing has yet been added to the folder. - return 0; - } - return getPageAt(lastPageIndex).getShortcutsAndWidgets().getChildCount() - + lastPageIndex * mOrganizer.getMaxItemsPerPage(); - } - /** * @return the rank of the cell nearest to the provided pixel position. */ diff --git a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java index c818462560..caf6e55b78 100644 --- a/src/com/android/launcher3/folder/PreviewItemDrawingParams.java +++ b/src/com/android/launcher3/folder/PreviewItemDrawingParams.java @@ -17,6 +17,8 @@ package com.android.launcher3.folder; import android.graphics.drawable.Drawable; +import com.android.launcher3.WorkspaceItemInfo; + /** * Manages the parameters used to draw a Folder preview item. */ @@ -25,9 +27,10 @@ class PreviewItemDrawingParams { float transY; float scale; float overlayAlpha; - FolderPreviewItemAnim anim; + public FolderPreviewItemAnim anim; public boolean hidden; - Drawable drawable; + public Drawable drawable; + public WorkspaceItemInfo item; PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) { this.transX = transX; diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java index 2ac6bf41d5..2d817e6d2d 100644 --- a/src/com/android/launcher3/folder/PreviewItemManager.java +++ b/src/com/android/launcher3/folder/PreviewItemManager.java @@ -23,28 +23,51 @@ import static com.android.launcher3.folder.FolderIcon.DROP_IN_ANIMATION_DURATION import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; import android.animation.ValueAnimator; +import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.util.FloatProperty; import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; -import com.android.launcher3.BubbleTextView; +import com.android.launcher3.Launcher; import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceItemInfo; +import com.android.launcher3.graphics.DrawableFactory; +import com.android.launcher3.graphics.PreloadIconDrawable; import java.util.ArrayList; import java.util.List; +import java.util.function.Predicate; /** * Manages the drawing and animations of {@link PreviewItemDrawingParams} for a {@link FolderIcon}. */ public class PreviewItemManager { - private FolderIcon mIcon; + private static final FloatProperty CURRENT_PAGE_ITEMS_TRANS_X = + new FloatProperty("currentPageItemsTransX") { + @Override + public void setValue(PreviewItemManager manager, float v) { + manager.mCurrentPageItemsTransX = v; + manager.onParamsChanged(); + } + + @Override + public Float get(PreviewItemManager manager) { + return manager.mCurrentPageItemsTransX; + } + }; + + private final Context mContext; + private final FolderIcon mIcon; + private final DrawableFactory mDrawableFactory; + private final int mIconSize; // These variables are all associated with the drawing of the preview; they are stored // as member variables for shared usage and to avoid computation on each frame @@ -69,7 +92,10 @@ public class PreviewItemManager { private static final int ITEM_SLIDE_IN_OUT_DISTANCE_PX = 200; public PreviewItemManager(FolderIcon icon) { + mContext = icon.getContext(); mIcon = icon; + mDrawableFactory = DrawableFactory.INSTANCE.get(mContext); + mIconSize = Launcher.getLauncher(mContext).getDeviceProfile().folderChildIconSizePx; } /** @@ -200,7 +226,7 @@ public class PreviewItemManager { } void buildParamsForPage(int page, ArrayList params, boolean animate) { - List items = mIcon.getPreviewIconsOnPage(page); + List items = mIcon.getPreviewItemsOnPage(page); int prevNumItems = params.size(); // We adjust the size of the list to match the number of items in the preview. @@ -214,13 +240,7 @@ public class PreviewItemManager { int numItemsInFirstPagePreview = page == 0 ? items.size() : MAX_NUM_ITEMS_IN_PREVIEW; for (int i = 0; i < params.size(); i++) { PreviewItemDrawingParams p = params.get(i); - p.drawable = items.get(i).getCompoundDrawables()[1]; - - if (p.drawable != null && !mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, items.get(i)); if (!animate) { computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, p); @@ -253,14 +273,8 @@ public class PreviewItemManager { buildParamsForPage(currentPage, mCurrentPageParams, false); onParamsChanged(); - ValueAnimator slideAnimator = ValueAnimator.ofFloat(0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); - slideAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - mCurrentPageItemsTransX = (float) valueAnimator.getAnimatedValue(); - onParamsChanged(); - } - }); + ValueAnimator slideAnimator = ObjectAnimator + .ofFloat(this, CURRENT_PAGE_ITEMS_TRANS_X, 0, ITEM_SLIDE_IN_OUT_DISTANCE_PX); slideAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { @@ -277,6 +291,25 @@ public class PreviewItemManager { buildParamsForPage(0, mFirstPageParams, animate); } + void updatePreviewItems(Predicate itemCheck) { + boolean modified = false; + for (PreviewItemDrawingParams param : mFirstPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + for (PreviewItemDrawingParams param : mCurrentPageParams) { + if (itemCheck.test(param.item)) { + setDrawable(param, param.item); + modified = true; + } + } + if (modified) { + mIcon.invalidate(); + } + } + boolean verifyDrawable(@NonNull Drawable who) { for (int i = 0; i < mFirstPageParams.size(); i++) { if (mFirstPageParams.get(i).drawable == who) { @@ -296,46 +329,46 @@ public class PreviewItemManager { * - Moving into a new position * - Moving out of the preview * - * @param oldParams The list of items in the old preview. - * @param newParams The list of items in the new preview. + * @param oldItems The list of items in the old preview. + * @param newItems The list of items in the new preview. * @param dropped The item that was dropped onto the FolderIcon. */ - public void onDrop(List oldParams, List newParams, + public void onDrop(List oldItems, List newItems, WorkspaceItemInfo dropped) { - int numItems = newParams.size(); + int numItems = newItems.size(); final ArrayList params = mFirstPageParams; buildParamsForPage(0, params, false); // New preview items for items that are moving in (except for the dropped item). - List moveIn = new ArrayList<>(); - for (BubbleTextView btv : newParams) { - if (!oldParams.contains(btv) && !btv.getTag().equals(dropped)) { - moveIn.add(btv); + List moveIn = new ArrayList<>(); + for (WorkspaceItemInfo newItem : newItems) { + if (!oldItems.contains(newItem) && !newItem.equals(dropped)) { + moveIn.add(newItem); } } for (int i = 0; i < moveIn.size(); ++i) { - int prevIndex = newParams.indexOf(moveIn.get(i)); + int prevIndex = newItems.indexOf(moveIn.get(i)); PreviewItemDrawingParams p = params.get(prevIndex); computePreviewItemDrawingParams(prevIndex, numItems, p); - updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newParams.indexOf(moveIn.get(i)), + updateTransitionParam(p, moveIn.get(i), ENTER_INDEX, newItems.indexOf(moveIn.get(i)), numItems); } // Items that are moving into new positions within the preview. - for (int newIndex = 0; newIndex < newParams.size(); ++newIndex) { - int oldIndex = oldParams.indexOf(newParams.get(newIndex)); + for (int newIndex = 0; newIndex < newItems.size(); ++newIndex) { + int oldIndex = oldItems.indexOf(newItems.get(newIndex)); if (oldIndex >= 0 && newIndex != oldIndex) { PreviewItemDrawingParams p = params.get(newIndex); - updateTransitionParam(p, newParams.get(newIndex), oldIndex, newIndex, numItems); + updateTransitionParam(p, newItems.get(newIndex), oldIndex, newIndex, numItems); } } // Old preview items that need to be moved out. - List moveOut = new ArrayList<>(oldParams); - moveOut.removeAll(newParams); + List moveOut = new ArrayList<>(oldItems); + moveOut.removeAll(newItems); for (int i = 0; i < moveOut.size(); ++i) { - BubbleTextView item = moveOut.get(i); - int oldIndex = oldParams.indexOf(item); + WorkspaceItemInfo item = moveOut.get(i); + int oldIndex = oldItems.indexOf(item); PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null); updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems); params.add(0, p); // We want these items first so that they are on drawn last. @@ -348,14 +381,9 @@ public class PreviewItemManager { } } - private void updateTransitionParam(final PreviewItemDrawingParams p, BubbleTextView btv, + private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item, int prevIndex, int newIndex, int numItems) { - p.drawable = btv.getCompoundDrawables()[1]; - if (!mIcon.mFolder.isOpen()) { - // Set the callback to FolderIcon as it is responsible to drawing the icon. The - // callback will be released when the folder is opened. - p.drawable.setCallback(mIcon); - } + setDrawable(p, item); FolderPreviewItemAnim anim = new FolderPreviewItemAnim(this, p, prevIndex, numItems, newIndex, numItems, DROP_IN_ANIMATION_DURATION, null); @@ -364,4 +392,20 @@ public class PreviewItemManager { } p.anim = anim; } + + private void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) { + if (item.hasPromiseIconUi()) { + PreloadIconDrawable drawable = mDrawableFactory.newPendingIcon(mContext, item); + drawable.setLevel(item.getInstallProgress()); + p.drawable = drawable; + } else { + p.drawable = mDrawableFactory.newIcon(mContext, item); + } + p.drawable.setBounds(0, 0, mIconSize, mIconSize); + p.item = item; + + // Set the callback to FolderIcon as it is responsible to drawing the icon. The + // callback will be released when the folder is opened. + p.drawable.setCallback(mIcon); + } }