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