1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.folder;
18 
19 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
20 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.animation.ObjectAnimator;
25 import android.content.Context;
26 import android.graphics.Canvas;
27 import android.graphics.Rect;
28 import android.graphics.drawable.Drawable;
29 import android.util.AttributeSet;
30 import android.util.Property;
31 import android.view.LayoutInflater;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.ViewConfiguration;
35 import android.view.ViewDebug;
36 import android.view.ViewGroup;
37 import android.widget.FrameLayout;
38 
39 import androidx.annotation.NonNull;
40 
41 import com.android.launcher3.Alarm;
42 import com.android.launcher3.AppInfo;
43 import com.android.launcher3.BubbleTextView;
44 import com.android.launcher3.CellLayout;
45 import com.android.launcher3.CheckLongPressHelper;
46 import com.android.launcher3.DeviceProfile;
47 import com.android.launcher3.DropTarget.DragObject;
48 import com.android.launcher3.FolderInfo;
49 import com.android.launcher3.FolderInfo.FolderListener;
50 import com.android.launcher3.ItemInfo;
51 import com.android.launcher3.Launcher;
52 import com.android.launcher3.LauncherSettings;
53 import com.android.launcher3.OnAlarmListener;
54 import com.android.launcher3.R;
55 import com.android.launcher3.SimpleOnStylusPressListener;
56 import com.android.launcher3.StylusEventHelper;
57 import com.android.launcher3.Utilities;
58 import com.android.launcher3.Workspace;
59 import com.android.launcher3.WorkspaceItemInfo;
60 import com.android.launcher3.anim.Interpolators;
61 import com.android.launcher3.config.FeatureFlags;
62 import com.android.launcher3.dot.FolderDotInfo;
63 import com.android.launcher3.dragndrop.BaseItemDragListener;
64 import com.android.launcher3.dragndrop.DragLayer;
65 import com.android.launcher3.dragndrop.DragView;
66 import com.android.launcher3.icons.DotRenderer;
67 import com.android.launcher3.touch.ItemClickHandler;
68 import com.android.launcher3.util.Executors;
69 import com.android.launcher3.util.Thunk;
70 import com.android.launcher3.views.IconLabelDotView;
71 import com.android.launcher3.widget.PendingAddShortcutInfo;
72 
73 import java.util.ArrayList;
74 import java.util.List;
75 import java.util.function.Predicate;
76 
77 /**
78  * An icon that can appear on in the workspace representing an {@link Folder}.
79  */
80 public class FolderIcon extends FrameLayout implements FolderListener, IconLabelDotView {
81 
82     @Thunk Launcher mLauncher;
83     @Thunk Folder mFolder;
84     private FolderInfo mInfo;
85 
86     private CheckLongPressHelper mLongPressHelper;
87     private StylusEventHelper mStylusEventHelper;
88 
89     static final int DROP_IN_ANIMATION_DURATION = 400;
90 
91     // Flag whether the folder should open itself when an item is dragged over is enabled.
92     public static final boolean SPRING_LOADING_ENABLED = true;
93 
94     // Delay when drag enters until the folder opens, in miliseconds.
95     private static final int ON_OPEN_DELAY = 800;
96 
97     @Thunk BubbleTextView mFolderName;
98 
99     PreviewBackground mBackground = new PreviewBackground();
100     private boolean mBackgroundIsVisible = true;
101 
102     FolderGridOrganizer mPreviewVerifier;
103     ClippedFolderIconLayoutRule mPreviewLayoutRule;
104     private PreviewItemManager mPreviewItemManager;
105     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
106     private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
107 
108     boolean mAnimating = false;
109 
110     private float mSlop;
111 
112     private Alarm mOpenAlarm = new Alarm();
113 
114     private boolean mForceHideDot;
115     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
116     private FolderDotInfo mDotInfo = new FolderDotInfo();
117     private DotRenderer mDotRenderer;
118     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
119     private DotRenderer.DrawParams mDotParams;
120     private float mDotScale;
121     private Animator mDotScaleAnim;
122 
123     private static final Property<FolderIcon, Float> DOT_SCALE_PROPERTY
124             = new Property<FolderIcon, Float>(Float.TYPE, "dotScale") {
125         @Override
126         public Float get(FolderIcon folderIcon) {
127             return folderIcon.mDotScale;
128         }
129 
130         @Override
131         public void set(FolderIcon folderIcon, Float value) {
132             folderIcon.mDotScale = value;
133             folderIcon.invalidate();
134         }
135     };
136 
FolderIcon(Context context, AttributeSet attrs)137     public FolderIcon(Context context, AttributeSet attrs) {
138         super(context, attrs);
139         init();
140     }
141 
FolderIcon(Context context)142     public FolderIcon(Context context) {
143         super(context);
144         init();
145     }
146 
init()147     private void init() {
148         mLongPressHelper = new CheckLongPressHelper(this);
149         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
150         mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
151         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
152         mPreviewItemManager = new PreviewItemManager(this);
153         mDotParams = new DotRenderer.DrawParams();
154     }
155 
fromXml(int resId, Launcher launcher, ViewGroup group, FolderInfo folderInfo)156     public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
157             FolderInfo folderInfo) {
158         @SuppressWarnings("all") // suppress dead code warning
159         final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
160         if (error) {
161             throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
162                     "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
163                     "is dependent on this");
164         }
165 
166         DeviceProfile grid = launcher.getWallpaperDeviceProfile();
167         FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
168                 .inflate(resId, group, false);
169 
170         icon.setClipToPadding(false);
171         icon.mFolderName = icon.findViewById(R.id.folder_icon_name);
172         icon.mFolderName.setText(folderInfo.title);
173         icon.mFolderName.setCompoundDrawablePadding(0);
174         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
175         lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
176 
177         icon.setTag(folderInfo);
178         icon.setOnClickListener(ItemClickHandler.INSTANCE);
179         icon.mInfo = folderInfo;
180         icon.mLauncher = launcher;
181         icon.mDotRenderer = grid.mDotRendererWorkSpace;
182         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
183         Folder folder = Folder.fromXml(launcher);
184         folder.setDragController(launcher.getDragController());
185         folder.setFolderIcon(icon);
186         folder.bind(folderInfo);
187         icon.setFolder(folder);
188         icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
189 
190         folderInfo.addListener(icon);
191 
192         icon.setOnFocusChangeListener(launcher.mFocusHandler);
193         return icon;
194     }
195 
animateBgShadowAndStroke()196     public void animateBgShadowAndStroke() {
197         mBackground.fadeInBackgroundShadow();
198         mBackground.animateBackgroundStroke();
199     }
200 
getFolderName()201     public BubbleTextView getFolderName() {
202         return mFolderName;
203     }
204 
getPreviewBounds(Rect outBounds)205     public void getPreviewBounds(Rect outBounds) {
206         mPreviewItemManager.recomputePreviewDrawingParams();
207         mBackground.getBounds(outBounds);
208     }
209 
getBackgroundStrokeWidth()210     public float getBackgroundStrokeWidth() {
211         return mBackground.getStrokeWidth();
212     }
213 
getFolder()214     public Folder getFolder() {
215         return mFolder;
216     }
217 
setFolder(Folder folder)218     private void setFolder(Folder folder) {
219         mFolder = folder;
220         mPreviewVerifier = new FolderGridOrganizer(mLauncher.getDeviceProfile().inv);
221         mPreviewVerifier.setFolderInfo(mFolder.getInfo());
222         updatePreviewItems(false);
223     }
224 
willAcceptItem(ItemInfo item)225     private boolean willAcceptItem(ItemInfo item) {
226         final int itemType = item.itemType;
227         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
228                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
229                 itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
230                 item != mInfo && !mFolder.isOpen());
231     }
232 
acceptDrop(ItemInfo dragInfo)233     public boolean acceptDrop(ItemInfo dragInfo) {
234         return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
235     }
236 
addItem(WorkspaceItemInfo item)237     public void addItem(WorkspaceItemInfo item) {
238         mInfo.add(item, true);
239     }
240 
removeItem(WorkspaceItemInfo item, boolean animate)241     public void removeItem(WorkspaceItemInfo item, boolean animate) {
242         mInfo.remove(item, animate);
243     }
244 
onDragEnter(ItemInfo dragInfo)245     public void onDragEnter(ItemInfo dragInfo) {
246         if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
247         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
248         CellLayout cl = (CellLayout) getParent().getParent();
249 
250         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
251         mOpenAlarm.setOnAlarmListener(mOnOpenListener);
252         if (SPRING_LOADING_ENABLED &&
253                 ((dragInfo instanceof AppInfo)
254                         || (dragInfo instanceof WorkspaceItemInfo)
255                         || (dragInfo instanceof PendingAddShortcutInfo))) {
256             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
257         }
258     }
259 
260     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
261         public void onAlarm(Alarm alarm) {
262             mFolder.beginExternalDrag();
263         }
264     };
265 
prepareCreateAnimation(final View destView)266     public Drawable prepareCreateAnimation(final View destView) {
267         return mPreviewItemManager.prepareCreateAnimation(destView);
268     }
269 
performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView, final WorkspaceItemInfo srcInfo, final DragView srcView, Rect dstRect, float scaleRelativeToDragLayer)270     public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
271             final WorkspaceItemInfo srcInfo, final DragView srcView, Rect dstRect,
272             float scaleRelativeToDragLayer) {
273         prepareCreateAnimation(destView);
274         addItem(destInfo);
275         // This will animate the first item from it's position as an icon into its
276         // position as the first item in the preview
277         mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null)
278                 .start();
279 
280         // This will animate the dragView (srcView) into the new folder
281         onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1,
282                 false /* itemReturnedOnFailedDrop */);
283     }
284 
performDestroyAnimation(Runnable onCompleteRunnable)285     public void performDestroyAnimation(Runnable onCompleteRunnable) {
286         // This will animate the final item in the preview to be full size.
287         mPreviewItemManager.createFirstItemAnimation(true /* reverse */, onCompleteRunnable)
288                 .start();
289     }
290 
onDragExit()291     public void onDragExit() {
292         mBackground.animateToRest();
293         mOpenAlarm.cancelAlarm();
294     }
295 
onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect, float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop)296     private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect,
297             float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
298         item.cellX = -1;
299         item.cellY = -1;
300 
301         // Typically, the animateView corresponds to the DragView; however, if this is being done
302         // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
303         // will not have a view to animate
304         if (animateView != null) {
305             DragLayer dragLayer = mLauncher.getDragLayer();
306             Rect from = new Rect();
307             dragLayer.getViewRectRelativeToSelf(animateView, from);
308             Rect to = finalRect;
309             if (to == null) {
310                 to = new Rect();
311                 Workspace workspace = mLauncher.getWorkspace();
312                 // Set cellLayout and this to it's final state to compute final animation locations
313                 workspace.setFinalTransitionTransform();
314                 float scaleX = getScaleX();
315                 float scaleY = getScaleY();
316                 setScaleX(1.0f);
317                 setScaleY(1.0f);
318                 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
319                 // Finished computing final animation locations, restore current state
320                 setScaleX(scaleX);
321                 setScaleY(scaleY);
322                 workspace.resetTransitionTransform();
323             }
324 
325             int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
326             boolean itemAdded = false;
327             if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
328                 List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
329                 mInfo.add(item, index, false);
330                 mCurrentPreviewItems.clear();
331                 mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
332 
333                 if (!oldPreviewItems.equals(mCurrentPreviewItems)) {
334                     int newIndex = mCurrentPreviewItems.indexOf(item);
335                     if (newIndex >= 0) {
336                         // If the item dropped is going to be in the preview, we update the
337                         // index here to reflect its position in the preview.
338                         index = newIndex;
339                     }
340 
341                     mPreviewItemManager.hidePreviewItem(index, true);
342                     mPreviewItemManager.onDrop(oldPreviewItems, mCurrentPreviewItems, item);
343                     itemAdded = true;
344                 } else {
345                     removeItem(item, false);
346                 }
347             }
348 
349             if (!itemAdded) {
350                 mInfo.add(item, index, true);
351             }
352 
353             int[] center = new int[2];
354             float scale = getLocalCenterForIndex(index, numItemsInPreview, center);
355             center[0] = Math.round(scaleRelativeToDragLayer * center[0]);
356             center[1] = Math.round(scaleRelativeToDragLayer * center[1]);
357 
358             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
359                     center[1] - animateView.getMeasuredHeight() / 2);
360 
361             float finalAlpha = index < MAX_NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
362 
363             float finalScale = scale * scaleRelativeToDragLayer;
364             dragLayer.animateView(animateView, from, to, finalAlpha,
365                     1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
366                     Interpolators.DEACCEL_2, Interpolators.ACCEL_2,
367                     null, DragLayer.ANIMATION_END_DISAPPEAR, null);
368 
369             mFolder.hideItem(item);
370 
371             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
372             final int finalIndex = index;
373 
374             String[] suggestedNameOut = new String[1];
375             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
376                 Executors.UI_HELPER_EXECUTOR.post(() -> mLauncher.getFolderNameProvider()
377                         .getSuggestedFolderName(getContext(), mInfo.contents, suggestedNameOut));
378             }
379             postDelayed(() -> {
380                 mPreviewItemManager.hidePreviewItem(finalIndex, false);
381                 mFolder.showItem(item);
382                 invalidate();
383                 mFolder.showSuggestedTitle(suggestedNameOut[0]);
384             }, DROP_IN_ANIMATION_DURATION);
385         } else {
386             addItem(item);
387         }
388     }
389 
onDrop(DragObject d, boolean itemReturnedOnFailedDrop)390     public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
391         WorkspaceItemInfo item;
392         if (d.dragInfo instanceof AppInfo) {
393             // Came from all apps -- make a copy
394             item = ((AppInfo) d.dragInfo).makeWorkspaceItem();
395         } else if (d.dragSource instanceof BaseItemDragListener){
396             // Came from a different window -- make a copy
397             item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
398         } else {
399             item = (WorkspaceItemInfo) d.dragInfo;
400         }
401         mFolder.notifyDrop();
402         onDrop(item, d.dragView, null, 1.0f,
403                 itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
404                 itemReturnedOnFailedDrop);
405     }
406 
setDotInfo(FolderDotInfo dotInfo)407     public void setDotInfo(FolderDotInfo dotInfo) {
408         updateDotScale(mDotInfo.hasDot(), dotInfo.hasDot());
409         mDotInfo = dotInfo;
410     }
411 
getLayoutRule()412     public ClippedFolderIconLayoutRule getLayoutRule() {
413         return mPreviewLayoutRule;
414     }
415 
416     @Override
setForceHideDot(boolean forceHideDot)417     public void setForceHideDot(boolean forceHideDot) {
418         if (mForceHideDot == forceHideDot) {
419             return;
420         }
421         mForceHideDot = forceHideDot;
422 
423         if (forceHideDot) {
424             invalidate();
425         } else if (hasDot()) {
426             animateDotScale(0, 1);
427         }
428     }
429 
430     /**
431      * Sets mDotScale to 1 or 0, animating if wasDotted or isDotted is false
432      * (the dot is being added or removed).
433      */
updateDotScale(boolean wasDotted, boolean isDotted)434     private void updateDotScale(boolean wasDotted, boolean isDotted) {
435         float newDotScale = isDotted ? 1f : 0f;
436         // Animate when a dot is first added or when it is removed.
437         if ((wasDotted ^ isDotted) && isShown()) {
438             animateDotScale(newDotScale);
439         } else {
440             cancelDotScaleAnim();
441             mDotScale = newDotScale;
442             invalidate();
443         }
444     }
445 
cancelDotScaleAnim()446     private void cancelDotScaleAnim() {
447         if (mDotScaleAnim != null) {
448             mDotScaleAnim.cancel();
449         }
450     }
451 
animateDotScale(float... dotScales)452     public void animateDotScale(float... dotScales) {
453         cancelDotScaleAnim();
454         mDotScaleAnim = ObjectAnimator.ofFloat(this, DOT_SCALE_PROPERTY, dotScales);
455         mDotScaleAnim.addListener(new AnimatorListenerAdapter() {
456             @Override
457             public void onAnimationEnd(Animator animation) {
458                 mDotScaleAnim = null;
459             }
460         });
461         mDotScaleAnim.start();
462     }
463 
hasDot()464     public boolean hasDot() {
465         return mDotInfo != null && mDotInfo.hasDot();
466     }
467 
getLocalCenterForIndex(int index, int curNumItems, int[] center)468     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
469         mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams(
470                 Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index), curNumItems, mTmpParams);
471 
472         mTmpParams.transX += mBackground.basePreviewOffsetX;
473         mTmpParams.transY += mBackground.basePreviewOffsetY;
474 
475         float intrinsicIconSize = mPreviewItemManager.getIntrinsicIconSize();
476         float offsetX = mTmpParams.transX + (mTmpParams.scale * intrinsicIconSize) / 2;
477         float offsetY = mTmpParams.transY + (mTmpParams.scale * intrinsicIconSize) / 2;
478 
479         center[0] = Math.round(offsetX);
480         center[1] = Math.round(offsetY);
481         return mTmpParams.scale;
482     }
483 
setFolderBackground(PreviewBackground bg)484     public void setFolderBackground(PreviewBackground bg) {
485         mBackground = bg;
486         mBackground.setInvalidateDelegate(this);
487     }
488 
489     @Override
setIconVisible(boolean visible)490     public void setIconVisible(boolean visible) {
491         mBackgroundIsVisible = visible;
492         invalidate();
493     }
494 
getFolderBackground()495     public PreviewBackground getFolderBackground() {
496         return mBackground;
497     }
498 
getPreviewItemManager()499     public PreviewItemManager getPreviewItemManager() {
500         return mPreviewItemManager;
501     }
502 
503     @Override
dispatchDraw(Canvas canvas)504     protected void dispatchDraw(Canvas canvas) {
505         super.dispatchDraw(canvas);
506 
507         if (!mBackgroundIsVisible) return;
508 
509         mPreviewItemManager.recomputePreviewDrawingParams();
510 
511         if (!mBackground.drawingDelegated()) {
512             mBackground.drawBackground(canvas);
513         }
514 
515         if (mCurrentPreviewItems.isEmpty() && !mAnimating) return;
516 
517         final int saveCount = canvas.save();
518         canvas.clipPath(mBackground.getClipPath());
519         mPreviewItemManager.draw(canvas);
520         canvas.restoreToCount(saveCount);
521 
522         if (!mBackground.drawingDelegated()) {
523             mBackground.drawBackgroundStroke(canvas);
524         }
525 
526         drawDot(canvas);
527     }
528 
drawDot(Canvas canvas)529     public void drawDot(Canvas canvas) {
530         if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
531             Rect iconBounds = mDotParams.iconBounds;
532             BubbleTextView.getIconBounds(this, iconBounds,
533                     mLauncher.getWallpaperDeviceProfile().iconSizePx);
534             float iconScale = (float) mBackground.previewSize / iconBounds.width();
535             Utilities.scaleRectAboutCenter(iconBounds, iconScale);
536 
537             // If we are animating to the accepting state, animate the dot out.
538             mDotParams.scale = Math.max(0, mDotScale - mBackground.getScaleProgress());
539             mDotParams.color = mBackground.getDotColor();
540             mDotRenderer.draw(canvas, mDotParams);
541         }
542     }
543 
setTextVisible(boolean visible)544     public void setTextVisible(boolean visible) {
545         if (visible) {
546             mFolderName.setVisibility(VISIBLE);
547         } else {
548             mFolderName.setVisibility(INVISIBLE);
549         }
550     }
551 
getTextVisible()552     public boolean getTextVisible() {
553         return mFolderName.getVisibility() == VISIBLE;
554     }
555 
556     /**
557      * Returns the list of items which should be visible in the preview
558      */
getPreviewItemsOnPage(int page)559     public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
560         return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.contents);
561     }
562 
563     @Override
verifyDrawable(@onNull Drawable who)564     protected boolean verifyDrawable(@NonNull Drawable who) {
565         return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who);
566     }
567 
568     @Override
onItemsChanged(boolean animate)569     public void onItemsChanged(boolean animate) {
570         updatePreviewItems(animate);
571         invalidate();
572         requestLayout();
573     }
574 
updatePreviewItems(boolean animate)575     private void updatePreviewItems(boolean animate) {
576         mPreviewItemManager.updatePreviewItems(animate);
577         mCurrentPreviewItems.clear();
578         mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
579     }
580 
581     /**
582      * Updates the preview items which match the provided condition
583      */
updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck)584     public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
585         mPreviewItemManager.updatePreviewItems(itemCheck);
586     }
587 
588     @Override
onAdd(WorkspaceItemInfo item, int rank)589     public void onAdd(WorkspaceItemInfo item, int rank) {
590         boolean wasDotted = mDotInfo.hasDot();
591         mDotInfo.addDotInfo(mLauncher.getDotInfoForItem(item));
592         boolean isDotted = mDotInfo.hasDot();
593         updateDotScale(wasDotted, isDotted);
594         invalidate();
595         requestLayout();
596     }
597 
598     @Override
onRemove(WorkspaceItemInfo item)599     public void onRemove(WorkspaceItemInfo item) {
600         boolean wasDotted = mDotInfo.hasDot();
601         mDotInfo.subtractDotInfo(mLauncher.getDotInfoForItem(item));
602         boolean isDotted = mDotInfo.hasDot();
603         updateDotScale(wasDotted, isDotted);
604         invalidate();
605         requestLayout();
606     }
607 
onTitleChanged(CharSequence title)608     public void onTitleChanged(CharSequence title) {
609         mFolderName.setText(title);
610         setContentDescription(getContext().getString(R.string.folder_name_format, title));
611     }
612 
613     @Override
onTouchEvent(MotionEvent event)614     public boolean onTouchEvent(MotionEvent event) {
615         // Call the superclass onTouchEvent first, because sometimes it changes the state to
616         // isPressed() on an ACTION_UP
617         boolean result = super.onTouchEvent(event);
618 
619         // Check for a stylus button press, if it occurs cancel any long press checks.
620         if (mStylusEventHelper.onMotionEvent(event)) {
621             mLongPressHelper.cancelLongPress();
622             return true;
623         }
624 
625         switch (event.getAction()) {
626             case MotionEvent.ACTION_DOWN:
627                 mLongPressHelper.postCheckForLongPress();
628                 break;
629             case MotionEvent.ACTION_CANCEL:
630             case MotionEvent.ACTION_UP:
631                 mLongPressHelper.cancelLongPress();
632                 break;
633             case MotionEvent.ACTION_MOVE:
634                 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
635                     mLongPressHelper.cancelLongPress();
636                 }
637                 break;
638         }
639         return result;
640     }
641 
642     @Override
cancelLongPress()643     public void cancelLongPress() {
644         super.cancelLongPress();
645         mLongPressHelper.cancelLongPress();
646     }
647 
removeListeners()648     public void removeListeners() {
649         mInfo.removeListener(this);
650         mInfo.removeListener(mFolder);
651     }
652 
clearLeaveBehindIfExists()653     public void clearLeaveBehindIfExists() {
654         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
655         if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
656             CellLayout cl = (CellLayout) getParent().getParent();
657             cl.clearFolderLeaveBehind();
658         }
659     }
660 
drawLeaveBehindIfExists()661     public void drawLeaveBehindIfExists() {
662         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
663         // While the folder is open, the position of the icon cannot change.
664         lp.canReorder = false;
665         if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
666             CellLayout cl = (CellLayout) getParent().getParent();
667             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
668         }
669     }
670 
onFolderClose(int currentPage)671     public void onFolderClose(int currentPage) {
672         mPreviewItemManager.onFolderClose(currentPage);
673     }
674 }
675