OSDN Git Service

Using DragObject for folder drop instead of maintaining states when
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / folder / FolderIcon.java
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 android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.animation.ValueAnimator.AnimatorUpdateListener;
23 import android.content.Context;
24 import android.graphics.Canvas;
25 import android.graphics.Color;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PorterDuff;
29 import android.graphics.Rect;
30 import android.graphics.Region;
31 import android.graphics.drawable.Drawable;
32 import android.os.Parcelable;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.ViewConfiguration;
39 import android.view.ViewGroup;
40 import android.view.animation.AccelerateInterpolator;
41 import android.view.animation.DecelerateInterpolator;
42 import android.widget.FrameLayout;
43 import android.widget.TextView;
44
45 import com.android.launcher3.Alarm;
46 import com.android.launcher3.AppInfo;
47 import com.android.launcher3.BubbleTextView;
48 import com.android.launcher3.CellLayout;
49 import com.android.launcher3.CheckLongPressHelper;
50 import com.android.launcher3.DeviceProfile;
51 import com.android.launcher3.DropTarget.DragObject;
52 import com.android.launcher3.FastBitmapDrawable;
53 import com.android.launcher3.FolderInfo;
54 import com.android.launcher3.FolderInfo.FolderListener;
55 import com.android.launcher3.IconCache;
56 import com.android.launcher3.ItemInfo;
57 import com.android.launcher3.Launcher;
58 import com.android.launcher3.LauncherAnimUtils;
59 import com.android.launcher3.LauncherAppState;
60 import com.android.launcher3.LauncherSettings;
61 import com.android.launcher3.OnAlarmListener;
62 import com.android.launcher3.PreloadIconDrawable;
63 import com.android.launcher3.R;
64 import com.android.launcher3.ShortcutInfo;
65 import com.android.launcher3.SimpleOnStylusPressListener;
66 import com.android.launcher3.StylusEventHelper;
67 import com.android.launcher3.Utilities;
68 import com.android.launcher3.Workspace;
69 import com.android.launcher3.config.FeatureFlags;
70 import com.android.launcher3.dragndrop.DragLayer;
71 import com.android.launcher3.dragndrop.DragView;
72 import com.android.launcher3.util.Thunk;
73
74 import java.util.ArrayList;
75
76 /**
77  * An icon that can appear on in the workspace representing an {@link Folder}.
78  */
79 public class FolderIcon extends FrameLayout implements FolderListener {
80     @Thunk Launcher mLauncher;
81     @Thunk Folder mFolder;
82     private FolderInfo mInfo;
83     @Thunk static boolean sStaticValuesDirty = true;
84
85     public static final int NUM_ITEMS_IN_PREVIEW = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
86             StackFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW :
87             ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
88
89     private CheckLongPressHelper mLongPressHelper;
90     private StylusEventHelper mStylusEventHelper;
91
92     // The number of icons to display in the
93     private static final int CONSUMPTION_ANIMATION_DURATION = 100;
94     private static final int DROP_IN_ANIMATION_DURATION = 400;
95     private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
96     private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
97
98     // Flag whether the folder should open itself when an item is dragged over is enabled.
99     public static final boolean SPRING_LOADING_ENABLED = true;
100
101     // Delay when drag enters until the folder opens, in miliseconds.
102     private static final int ON_OPEN_DELAY = 800;
103
104     @Thunk BubbleTextView mFolderName;
105
106     // These variables are all associated with the drawing of the preview; they are stored
107     // as member variables for shared usage and to avoid computation on each frame
108     private int mIntrinsicIconSize = -1;
109     private int mTotalWidth = -1;
110     private int mPrevTopPadding = -1;
111
112     PreviewBackground mBackground = new PreviewBackground();
113
114     private PreviewLayoutRule mPreviewLayoutRule;
115
116     boolean mAnimating = false;
117     private Rect mOldBounds = new Rect();
118
119     private float mSlop;
120
121     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
122     private ArrayList<PreviewItemDrawingParams> mDrawingParams = new ArrayList<PreviewItemDrawingParams>();
123     private Drawable mReferenceDrawable = null;
124
125     Paint mBgPaint = new Paint();
126
127     private Alarm mOpenAlarm = new Alarm();
128
129     public FolderIcon(Context context, AttributeSet attrs) {
130         super(context, attrs);
131         init();
132     }
133
134     public FolderIcon(Context context) {
135         super(context);
136         init();
137     }
138
139     private void init() {
140         mLongPressHelper = new CheckLongPressHelper(this);
141         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
142         mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
143                 new StackFolderIconLayoutRule() :
144                 new ClippedFolderIconLayoutRule();
145     }
146
147     public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
148             FolderInfo folderInfo, IconCache iconCache) {
149         @SuppressWarnings("all") // suppress dead code warning
150         final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
151         if (error) {
152             throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
153                     "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
154                     "is dependent on this");
155         }
156
157         DeviceProfile grid = launcher.getDeviceProfile();
158         FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
159
160         // For performance and compatibility reasons we render the preview using a software layer.
161         // In particular, hardware path clipping has spotty ecosystem support and bad performance.
162         // Software rendering also allows us to use shadow layers.
163         icon.setLayerType(LAYER_TYPE_SOFTWARE, new Paint(Paint.FILTER_BITMAP_FLAG));
164
165         icon.setClipToPadding(false);
166         icon.mFolderName = (BubbleTextView) 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(launcher);
174         icon.mInfo = folderInfo;
175         icon.mLauncher = launcher;
176         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
177         Folder folder = Folder.fromXml(launcher);
178         folder.setDragController(launcher.getDragController());
179         folder.setFolderIcon(icon);
180         folder.bind(folderInfo);
181         icon.setFolder(folder);
182         icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
183
184         folderInfo.addListener(icon);
185
186         icon.setOnFocusChangeListener(launcher.mFocusHandler);
187         return icon;
188     }
189
190     @Override
191     protected Parcelable onSaveInstanceState() {
192         sStaticValuesDirty = true;
193         return super.onSaveInstanceState();
194     }
195
196     public Folder getFolder() {
197         return mFolder;
198     }
199
200     private void setFolder(Folder folder) {
201         mFolder = folder;
202         updateItemDrawingParams(false);
203     }
204
205     public FolderInfo getFolderInfo() {
206         return mInfo;
207     }
208
209     private boolean willAcceptItem(ItemInfo item) {
210         final int itemType = item.itemType;
211         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
212                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
213                 itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
214                 !mFolder.isFull() && item != mInfo && !mInfo.opened);
215     }
216
217     public boolean acceptDrop(ItemInfo dragInfo) {
218         final ItemInfo item = dragInfo;
219         return !mFolder.isDestroyed() && willAcceptItem(item);
220     }
221
222     public void addItem(ShortcutInfo item) {
223         mInfo.add(item, true);
224     }
225
226     public void onDragEnter(ItemInfo dragInfo) {
227         if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
228         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
229         CellLayout cl = (CellLayout) getParent().getParent();
230
231         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
232         mOpenAlarm.setOnAlarmListener(mOnOpenListener);
233         if (SPRING_LOADING_ENABLED &&
234                 ((dragInfo instanceof AppInfo) || (dragInfo instanceof ShortcutInfo))) {
235             // TODO: we currently don't support spring-loading for PendingAddShortcutInfos even
236             // though widget-style shortcuts can be added to folders. The issue is that we need
237             // to deal with configuration activities which are currently handled in
238             // Workspace#onDropExternal.
239             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
240         }
241     }
242
243     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
244         public void onAlarm(Alarm alarm) {
245             mFolder.beginExternalDrag();
246             mLauncher.openFolder(FolderIcon.this);
247         }
248     };
249
250     public Drawable prepareCreate(final View destView) {
251         Drawable animateDrawable = getTopDrawable((TextView) destView);
252         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
253                 destView.getMeasuredWidth());
254         return animateDrawable;
255     }
256
257     public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
258             final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
259             float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
260
261         // These correspond two the drawable and view that the icon was dropped _onto_
262         Drawable animateDrawable = prepareCreate(destView);
263
264         mReferenceDrawable = animateDrawable;
265
266         addItem(destInfo);
267         // This will animate the first item from it's position as an icon into its
268         // position as the first item in the preview
269         animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
270
271         // This will animate the dragView (srcView) into the new folder
272         onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
273     }
274
275     public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
276         Drawable animateDrawable = getTopDrawable((TextView) finalView);
277         computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
278                 finalView.getMeasuredWidth());
279
280         // This will animate the first item from it's position as an icon into its
281         // position as the first item in the preview
282         animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
283                 onCompleteRunnable);
284     }
285
286     public void onDragExit() {
287         mBackground.animateToRest();
288         mOpenAlarm.cancelAlarm();
289     }
290
291     private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
292             float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
293         item.cellX = -1;
294         item.cellY = -1;
295
296         // Typically, the animateView corresponds to the DragView; however, if this is being done
297         // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
298         // will not have a view to animate
299         if (animateView != null) {
300             DragLayer dragLayer = mLauncher.getDragLayer();
301             Rect from = new Rect();
302             dragLayer.getViewRectRelativeToSelf(animateView, from);
303             Rect to = finalRect;
304             if (to == null) {
305                 to = new Rect();
306                 Workspace workspace = mLauncher.getWorkspace();
307                 // Set cellLayout and this to it's final state to compute final animation locations
308                 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
309                 float scaleX = getScaleX();
310                 float scaleY = getScaleY();
311                 setScaleX(1.0f);
312                 setScaleY(1.0f);
313                 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
314                 // Finished computing final animation locations, restore current state
315                 setScaleX(scaleX);
316                 setScaleY(scaleY);
317                 workspace.resetTransitionTransform((CellLayout) getParent().getParent());
318             }
319
320             int[] center = new int[2];
321             float scale = getLocalCenterForIndex(index, index + 1, center);
322             center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
323             center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
324
325             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
326                       center[1] - animateView.getMeasuredHeight() / 2);
327
328             float finalAlpha = index < mPreviewLayoutRule.numItems() ? 0.5f : 0f;
329
330             float finalScale = scale * scaleRelativeToDragLayer;
331             dragLayer.animateView(animateView, from, to, finalAlpha,
332                     1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
333                     new DecelerateInterpolator(2), new AccelerateInterpolator(2),
334                     postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
335             addItem(item);
336             mFolder.hideItem(item);
337
338             final PreviewItemDrawingParams params = index < mDrawingParams.size() ?
339                     mDrawingParams.get(index) : null;
340             if (params != null) params.hidden = true;
341             postDelayed(new Runnable() {
342                 public void run() {
343                     if (params != null) params.hidden = false;
344                     mFolder.showItem(item);
345                     invalidate();
346                 }
347             }, DROP_IN_ANIMATION_DURATION);
348         } else {
349             addItem(item);
350         }
351     }
352
353     public void onDrop(DragObject d) {
354         ShortcutInfo item;
355         if (d.dragInfo instanceof AppInfo) {
356             // Came from all apps -- make a copy
357             item = ((AppInfo) d.dragInfo).makeShortcut();
358         } else {
359             item = (ShortcutInfo) d.dragInfo;
360         }
361         mFolder.notifyDrop();
362         onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
363     }
364
365     private void computePreviewDrawingParams(int drawableSize, int totalSize) {
366         if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize ||
367                 mPrevTopPadding != getPaddingTop()) {
368             DeviceProfile grid = mLauncher.getDeviceProfile();
369
370             mIntrinsicIconSize = drawableSize;
371             mTotalWidth = totalSize;
372             mPrevTopPadding = getPaddingTop();
373
374             mBackground.setup(getResources().getDisplayMetrics(), grid, this, mTotalWidth,
375                     getPaddingTop());
376             mPreviewLayoutRule.init(mBackground.previewSize, mIntrinsicIconSize,
377                     Utilities.isRtl(getResources()));
378
379             updateItemDrawingParams(false);
380         }
381     }
382
383     private void computePreviewDrawingParams(Drawable d) {
384         computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
385     }
386
387     static class PreviewItemDrawingParams {
388         PreviewItemDrawingParams(float transX, float transY, float scale, float overlayAlpha) {
389             this.transX = transX;
390             this.transY = transY;
391             this.scale = scale;
392             this.overlayAlpha = overlayAlpha;
393         }
394
395         public void update(float transX, float transY, float scale) {
396             // We ensure the update will not interfere with an animation on the layout params
397             // If the final values differ, we cancel the animation.
398             if (anim != null) {
399                 if (anim.finalTransX == transX || anim.finalTransY == transY
400                         || anim.finalScale == scale) {
401                     return;
402                 }
403                 anim.cancel();
404             }
405
406             this.transX = transX;
407             this.transY = transY;
408             this.scale = scale;
409         }
410
411         float transX;
412         float transY;
413         float scale;
414         public float overlayAlpha;
415         boolean hidden;
416         FolderPreviewItemAnim anim;
417         Drawable drawable;
418     }
419
420     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
421         mTmpParams = computePreviewItemDrawingParams(Math.min(mPreviewLayoutRule.numItems(), index),
422                 curNumItems, mTmpParams);
423
424         mTmpParams.transX += mBackground.basePreviewOffsetX;
425         mTmpParams.transY += mBackground.basePreviewOffsetY;
426         float offsetX = mTmpParams.transX + (mTmpParams.scale * mIntrinsicIconSize) / 2;
427         float offsetY = mTmpParams.transY + (mTmpParams.scale * mIntrinsicIconSize) / 2;
428
429         center[0] = (int) Math.round(offsetX);
430         center[1] = (int) Math.round(offsetY);
431         return mTmpParams.scale;
432     }
433
434     private PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
435             PreviewItemDrawingParams params) {
436         // We use an index of -1 to represent an icon on the workspace for the destroy and
437         // create animations
438         if (index == -1) {
439             return getFinalIconParams(params);
440         }
441         return mPreviewLayoutRule.computePreviewItemDrawingParams(index, curNumItems, params);
442     }
443
444     private PreviewItemDrawingParams getFinalIconParams(PreviewItemDrawingParams params) {
445         float iconSize = mLauncher.getDeviceProfile().iconSizePx;
446
447         final float scale = iconSize / mReferenceDrawable.getIntrinsicWidth();
448         final float trans = (mBackground.previewSize - iconSize) / 2;
449
450         params.update(trans, trans, scale);
451         return params;
452     }
453
454     private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
455         canvas.save();
456         canvas.translate(params.transX, params.transY);
457         canvas.scale(params.scale, params.scale);
458         Drawable d = params.drawable;
459
460         if (d != null) {
461             mOldBounds.set(d.getBounds());
462             d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
463             if (d instanceof FastBitmapDrawable) {
464                 FastBitmapDrawable fd = (FastBitmapDrawable) d;
465                 float oldBrightness = fd.getBrightness();
466                 fd.setBrightness(params.overlayAlpha);
467                 d.draw(canvas);
468                 fd.setBrightness(oldBrightness);
469             } else {
470                 d.setColorFilter(Color.argb((int) (params.overlayAlpha * 255), 255, 255, 255),
471                         PorterDuff.Mode.SRC_ATOP);
472                 d.draw(canvas);
473                 d.clearColorFilter();
474             }
475             d.setBounds(mOldBounds);
476         }
477         canvas.restore();
478     }
479
480     /**
481      * This object represents a FolderIcon preview background. It stores drawing / measurement
482      * information, handles drawing, and animation (accept state <--> rest state).
483      */
484     public static class PreviewBackground {
485         private float mScale = 1f;
486         private float mColorMultiplier = 1f;
487         private Path mClipPath = new Path();
488         private int mStrokeWidth;
489         private View mInvalidateDelegate;
490
491         public int previewSize;
492         private int basePreviewOffsetX;
493         private int basePreviewOffsetY;
494
495         private CellLayout mDrawingDelegate;
496         public int delegateCellX;
497         public int delegateCellY;
498
499         // When the PreviewBackground is drawn under an icon (for creating a folder) the border
500         // should not occlude the icon
501         public boolean isClipping = true;
502
503         // Drawing / animation configurations
504         private static final float ACCEPT_SCALE_FACTOR = 1.25f;
505         private static final float ACCEPT_COLOR_MULTIPLIER = 1.5f;
506
507         // Expressed on a scale from 0 to 255.
508         private static final int BG_OPACITY = 160;
509         private static final int MAX_BG_OPACITY = 225;
510         private static final int BG_INTENSITY = 245;
511         private static final int SHADOW_OPACITY = 80;
512
513         ValueAnimator mScaleAnimator;
514
515         public void setup(DisplayMetrics dm, DeviceProfile grid, View invalidateDelegate,
516                    int availableSpace, int topPadding) {
517             mInvalidateDelegate = invalidateDelegate;
518
519             final int previewSize = grid.folderIconSizePx;
520             final int previewPadding = grid.folderIconPreviewPadding;
521
522             this.previewSize = (previewSize - 2 * previewPadding);
523
524             basePreviewOffsetX = (availableSpace - this.previewSize) / 2;
525             basePreviewOffsetY = previewPadding + grid.folderBackgroundOffset + topPadding;
526
527             mStrokeWidth = Utilities.pxFromDp(1, dm);
528
529             invalidate();
530         }
531
532         int getRadius() {
533             return previewSize / 2;
534         }
535
536         int getScaledRadius() {
537             return (int) (mScale * getRadius());
538         }
539
540         int getOffsetX() {
541             return basePreviewOffsetX - (getScaledRadius() - getRadius());
542         }
543
544         int getOffsetY() {
545             return basePreviewOffsetY - (getScaledRadius() - getRadius());
546         }
547
548         void invalidate() {
549             int radius = getScaledRadius();
550             mClipPath.reset();
551             mClipPath.addCircle(radius, radius, radius, Path.Direction.CW);
552
553             if (mInvalidateDelegate != null) {
554                 mInvalidateDelegate.invalidate();
555             }
556
557             if (mDrawingDelegate != null) {
558                 mDrawingDelegate.invalidate();
559             }
560         }
561
562         void setInvalidateDelegate(View invalidateDelegate) {
563             mInvalidateDelegate = invalidateDelegate;
564             invalidate();
565         }
566
567         public void drawBackground(Canvas canvas, Paint paint) {
568             canvas.save();
569             canvas.translate(getOffsetX(), getOffsetY());
570
571             paint.reset();
572             paint.setStyle(Paint.Style.FILL);
573             paint.setXfermode(null);
574             paint.setAntiAlias(true);
575
576             int alpha = (int) Math.min(MAX_BG_OPACITY, BG_OPACITY * mColorMultiplier);
577             paint.setColor(Color.argb(alpha, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
578
579             float radius = getScaledRadius();
580
581             canvas.drawCircle(radius, radius, radius, paint);
582             canvas.clipPath(mClipPath, Region.Op.DIFFERENCE);
583
584             paint.setStyle(Paint.Style.STROKE);
585             paint.setColor(Color.TRANSPARENT);
586             paint.setShadowLayer(mStrokeWidth, 0, mStrokeWidth, Color.argb(SHADOW_OPACITY, 0, 0, 0));
587             canvas.drawCircle(radius, radius, radius, paint);
588
589             canvas.restore();
590         }
591
592         public void drawBackgroundStroke(Canvas canvas, Paint paint) {
593             canvas.save();
594             canvas.translate(getOffsetX(), getOffsetY());
595
596             paint.reset();
597             paint.setAntiAlias(true);
598             paint.setColor(Color.argb(255, BG_INTENSITY, BG_INTENSITY, BG_INTENSITY));
599             paint.setStyle(Paint.Style.STROKE);
600             paint.setStrokeWidth(mStrokeWidth);
601
602             float radius = getScaledRadius();
603             canvas.drawCircle(radius, radius, radius - 1, paint);
604
605             canvas.restore();
606         }
607
608         public void drawLeaveBehind(Canvas canvas, Paint paint) {
609             float originalScale = mScale;
610             mScale = 0.5f;
611
612             canvas.save();
613             canvas.translate(getOffsetX(), getOffsetY());
614
615             paint.reset();
616             paint.setAntiAlias(true);
617             paint.setColor(Color.argb(160, 245, 245, 245));
618
619             float radius = getScaledRadius();
620             canvas.drawCircle(radius, radius, radius, paint);
621
622             canvas.restore();
623             mScale = originalScale;
624         }
625
626         // It is the callers responsibility to save and restore the canvas.
627         private void clipCanvas(Canvas canvas) {
628             canvas.translate(getOffsetX(), getOffsetY());
629             canvas.clipPath(mClipPath);
630             canvas.translate(-getOffsetX(), -getOffsetY());
631         }
632
633         private void delegateDrawing(CellLayout delegate, int cellX, int cellY) {
634             if (mDrawingDelegate != delegate) {
635                 delegate.addFolderBackground(this);
636             }
637
638             mDrawingDelegate = delegate;
639             delegateCellX = cellX;
640             delegateCellY = cellY;
641
642             invalidate();
643         }
644
645         private void clearDrawingDelegate() {
646             if (mDrawingDelegate != null) {
647                 mDrawingDelegate.removeFolderBackground(this);
648             }
649
650             mDrawingDelegate = null;
651             invalidate();
652         }
653
654         private boolean drawingDelegated() {
655             return mDrawingDelegate != null;
656         }
657
658         private void animateScale(float finalScale, float finalMultiplier,
659                 final Runnable onStart, final Runnable onEnd) {
660             final float scale0 = mScale;
661             final float scale1 = finalScale;
662
663             final float bgMultiplier0 = mColorMultiplier;
664             final float bgMultiplier1 = finalMultiplier;
665
666             if (mScaleAnimator != null) {
667                 mScaleAnimator.cancel();
668             }
669
670             mScaleAnimator = LauncherAnimUtils.ofFloat(null, 0f, 1.0f);
671
672             mScaleAnimator.addUpdateListener(new AnimatorUpdateListener() {
673                 @Override
674                 public void onAnimationUpdate(ValueAnimator animation) {
675                     float prog = animation.getAnimatedFraction();
676                     mScale = prog * scale1 + (1 - prog) * scale0;
677                     mColorMultiplier = prog * bgMultiplier1 + (1 - prog) * bgMultiplier0;
678                     invalidate();
679                 }
680             });
681             mScaleAnimator.addListener(new AnimatorListenerAdapter() {
682                 @Override
683                 public void onAnimationStart(Animator animation) {
684                     if (onStart != null) {
685                         onStart.run();
686                     }
687                 }
688
689                 @Override
690                 public void onAnimationEnd(Animator animation) {
691                     if (onEnd != null) {
692                         onEnd.run();
693                     }
694                     mScaleAnimator = null;
695                 }
696             });
697
698             mScaleAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
699             mScaleAnimator.start();
700         }
701
702         public void animateToAccept(final CellLayout cl, final int cellX, final int cellY) {
703             Runnable onStart = new Runnable() {
704                 @Override
705                 public void run() {
706                     delegateDrawing(cl, cellX, cellY);
707                 }
708             };
709             animateScale(ACCEPT_SCALE_FACTOR, ACCEPT_COLOR_MULTIPLIER, onStart, null);
710         }
711
712         public void animateToRest() {
713             // This can be called multiple times -- we need to make sure the drawing delegate
714             // is saved and restored at the beginning of the animation, since cancelling the
715             // existing animation can clear the delgate.
716             final CellLayout cl = mDrawingDelegate;
717             final int cellX = delegateCellX;
718             final int cellY = delegateCellY;
719
720             Runnable onStart = new Runnable() {
721                 @Override
722                 public void run() {
723                     delegateDrawing(cl, cellX, cellY);
724                 }
725             };
726             Runnable onEnd = new Runnable() {
727                 @Override
728                 public void run() {
729                     clearDrawingDelegate();
730                 }
731             };
732             animateScale(1f, 1f, onStart, onEnd);
733         }
734     }
735
736     public void setFolderBackground(PreviewBackground bg) {
737         mBackground = bg;
738         mBackground.setInvalidateDelegate(this);
739     }
740
741     @Override
742     protected void dispatchDraw(Canvas canvas) {
743         super.dispatchDraw(canvas);
744
745         if (mReferenceDrawable != null) {
746             computePreviewDrawingParams(mReferenceDrawable);
747         }
748
749         if (!mBackground.drawingDelegated()) {
750             mBackground.drawBackground(canvas, mBgPaint);
751         }
752
753         if (mFolder == null) return;
754         if (mFolder.getItemCount() == 0 && !mAnimating) return;
755
756         canvas.save();
757
758
759         if (mPreviewLayoutRule.clipToBackground()) {
760             mBackground.clipCanvas(canvas);
761         }
762
763         // The items are drawn in coordinates relative to the preview offset
764         canvas.translate(mBackground.basePreviewOffsetX, mBackground.basePreviewOffsetY);
765
766         // The first item should be drawn last (ie. on top of later items)
767         for (int i = mDrawingParams.size() - 1; i >= 0; i--) {
768             PreviewItemDrawingParams p = mDrawingParams.get(i);
769             if (!p.hidden) {
770                 drawPreviewItem(canvas, p);
771             }
772         }
773         canvas.restore();
774
775         if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
776             mBackground.drawBackgroundStroke(canvas, mBgPaint);
777         }
778     }
779
780     private Drawable getTopDrawable(TextView v) {
781         Drawable d = v.getCompoundDrawables()[1];
782         return (d instanceof PreloadIconDrawable) ? ((PreloadIconDrawable) d).mIcon : d;
783     }
784
785     class FolderPreviewItemAnim {
786         ValueAnimator mValueAnimator;
787         float finalScale;
788         float finalTransX;
789         float finalTransY;
790
791         /**
792          *
793          * @param params layout params to animate
794          * @param index0 original index of the item to be animated
795          * @param nItems0 original number of items in the preview
796          * @param index1 new index of the item to be animated
797          * @param nItems1 new number of items in the preview
798          * @param duration duration in ms of the animation
799          * @param onCompleteRunnable runnable to execute upon animation completion
800          */
801         public FolderPreviewItemAnim(final PreviewItemDrawingParams params, int index0, int nItems0,
802                 int index1, int nItems1, int duration, final Runnable onCompleteRunnable) {
803
804             computePreviewItemDrawingParams(index1, nItems1, mTmpParams);
805
806             finalScale = mTmpParams.scale;
807             finalTransX = mTmpParams.transX;
808             finalTransY = mTmpParams.transY;
809
810             computePreviewItemDrawingParams(index0, nItems0, mTmpParams);
811
812             final float scale0 = mTmpParams.scale;
813             final float transX0 = mTmpParams.transX;
814             final float transY0 = mTmpParams.transY;
815
816             mValueAnimator = LauncherAnimUtils.ofFloat(FolderIcon.this, 0f, 1.0f);
817             mValueAnimator.addUpdateListener(new AnimatorUpdateListener(){
818                 public void onAnimationUpdate(ValueAnimator animation) {
819                     float progress = animation.getAnimatedFraction();
820
821                     params.transX = transX0 + progress * (finalTransX - transX0);
822                     params.transY = transY0 + progress * (finalTransY - transY0);
823                     params.scale = scale0 + progress * (finalScale - scale0);
824                     invalidate();
825                 }
826             });
827
828             mValueAnimator.addListener(new AnimatorListenerAdapter() {
829                 @Override
830                 public void onAnimationStart(Animator animation) {
831                 }
832
833                 @Override
834                 public void onAnimationEnd(Animator animation) {
835                     if (onCompleteRunnable != null) {
836                         onCompleteRunnable.run();
837                     }
838                     params.anim = null;
839                 }
840             });
841             mValueAnimator.setDuration(duration);
842         }
843
844         public void start() {
845             mValueAnimator.start();
846         }
847
848         public void cancel() {
849             mValueAnimator.cancel();
850         }
851
852         public boolean hasEqualFinalState(FolderPreviewItemAnim anim) {
853             return finalTransY == anim.finalTransY && finalTransX == anim.finalTransX &&
854                     finalScale == anim.finalScale;
855
856         }
857     }
858
859     private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
860             final Runnable onCompleteRunnable) {
861
862         FolderPreviewItemAnim anim;
863         if (!reverse) {
864             anim = new FolderPreviewItemAnim(mDrawingParams.get(0), -1, -1, 0, 2, duration,
865                     onCompleteRunnable);
866         } else {
867             anim = new FolderPreviewItemAnim(mDrawingParams.get(0), 0, 2, -1, -1, duration,
868                     onCompleteRunnable);
869         }
870         anim.start();
871     }
872
873     public void setTextVisible(boolean visible) {
874         if (visible) {
875             mFolderName.setVisibility(VISIBLE);
876         } else {
877             mFolderName.setVisibility(INVISIBLE);
878         }
879     }
880
881     public boolean getTextVisible() {
882         return mFolderName.getVisibility() == VISIBLE;
883     }
884
885     private void updateItemDrawingParams(boolean animate) {
886         ArrayList<View> items = mFolder.getItemsInReadingOrder();
887         int nItemsInPreview = Math.min(items.size(), mPreviewLayoutRule.numItems());
888
889         int prevNumItems = mDrawingParams.size();
890
891         // We adjust the size of the list to match the number of items in the preview
892         while (nItemsInPreview < mDrawingParams.size()) {
893             mDrawingParams.remove(mDrawingParams.size() - 1);
894         }
895         while (nItemsInPreview > mDrawingParams.size()) {
896             mDrawingParams.add(new PreviewItemDrawingParams(0, 0, 0, 0));
897         }
898
899         for (int i = 0; i < mDrawingParams.size(); i++) {
900             PreviewItemDrawingParams p = mDrawingParams.get(i);
901             p.drawable = getTopDrawable((TextView) items.get(i));
902
903             if (!animate || FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON) {
904                 computePreviewItemDrawingParams(i, nItemsInPreview, p);
905                 if (mReferenceDrawable == null) {
906                     mReferenceDrawable = p.drawable;
907                 }
908             } else {
909                 FolderPreviewItemAnim anim = new FolderPreviewItemAnim(p, i, prevNumItems, i,
910                         nItemsInPreview, DROP_IN_ANIMATION_DURATION, null);
911
912                 if (p.anim != null) {
913                     if (p.anim.hasEqualFinalState(anim)) {
914                         // do nothing, let the current animation finish
915                         continue;
916                     }
917                     p.anim.cancel();
918                 }
919                 p.anim = anim;
920                 p.anim.start();
921             }
922         }
923     }
924
925     @Override
926     public void onItemsChanged(boolean animate) {
927         updateItemDrawingParams(animate);
928         invalidate();
929         requestLayout();
930     }
931
932     public void onAdd(ShortcutInfo item) {
933         invalidate();
934         requestLayout();
935     }
936
937     public void onRemove(ShortcutInfo item) {
938         invalidate();
939         requestLayout();
940     }
941
942     public void onTitleChanged(CharSequence title) {
943         mFolderName.setText(title);
944         setContentDescription(getContext().getString(R.string.folder_name_format, title));
945     }
946
947     @Override
948     public boolean onTouchEvent(MotionEvent event) {
949         // Call the superclass onTouchEvent first, because sometimes it changes the state to
950         // isPressed() on an ACTION_UP
951         boolean result = super.onTouchEvent(event);
952
953         // Check for a stylus button press, if it occurs cancel any long press checks.
954         if (mStylusEventHelper.onMotionEvent(event)) {
955             mLongPressHelper.cancelLongPress();
956             return true;
957         }
958
959         switch (event.getAction()) {
960             case MotionEvent.ACTION_DOWN:
961                 mLongPressHelper.postCheckForLongPress();
962                 break;
963             case MotionEvent.ACTION_CANCEL:
964             case MotionEvent.ACTION_UP:
965                 mLongPressHelper.cancelLongPress();
966                 break;
967             case MotionEvent.ACTION_MOVE:
968                 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
969                     mLongPressHelper.cancelLongPress();
970                 }
971                 break;
972         }
973         return result;
974     }
975
976     @Override
977     protected void onAttachedToWindow() {
978         super.onAttachedToWindow();
979         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
980     }
981
982     @Override
983     public void cancelLongPress() {
984         super.cancelLongPress();
985         mLongPressHelper.cancelLongPress();
986     }
987
988     public void removeListeners() {
989         mInfo.removeListener(this);
990         mInfo.removeListener(mFolder);
991     }
992
993     public interface PreviewLayoutRule {
994         public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
995             PreviewItemDrawingParams params);
996
997         public void init(int availableSpace, int intrinsicIconSize, boolean rtl);
998
999         public int numItems();
1000         public boolean clipToBackground();
1001     }
1002 }