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
17package com.android.launcher2;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.animation.ValueAnimator.AnimatorUpdateListener;
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.PorterDuff;
28import android.graphics.Rect;
29import android.graphics.drawable.Drawable;
30import android.os.Parcelable;
31import android.util.AttributeSet;
32import android.view.LayoutInflater;
33import android.view.MotionEvent;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.animation.AccelerateInterpolator;
37import android.view.animation.DecelerateInterpolator;
38import android.widget.ImageView;
39import android.widget.LinearLayout;
40import android.widget.TextView;
41
42import com.android.launcher.R;
43import com.android.launcher2.DropTarget.DragObject;
44import com.android.launcher2.FolderInfo.FolderListener;
45
46import java.util.ArrayList;
47
48/**
49 * An icon that can appear on in the workspace representing an {@link UserFolder}.
50 */
51public class FolderIcon extends LinearLayout implements FolderListener {
52    private Launcher mLauncher;
53    private Folder mFolder;
54    private FolderInfo mInfo;
55    private static boolean sStaticValuesDirty = true;
56
57    private CheckLongPressHelper mLongPressHelper;
58
59    // The number of icons to display in the
60    private static final int NUM_ITEMS_IN_PREVIEW = 3;
61    private static final int CONSUMPTION_ANIMATION_DURATION = 100;
62    private static final int DROP_IN_ANIMATION_DURATION = 400;
63    private static final int INITIAL_ITEM_ANIMATION_DURATION = 350;
64    private static final int FINAL_ITEM_ANIMATION_DURATION = 200;
65
66    // The degree to which the inner ring grows when accepting drop
67    private static final float INNER_RING_GROWTH_FACTOR = 0.15f;
68
69    // The degree to which the outer ring is scaled in its natural state
70    private static final float OUTER_RING_GROWTH_FACTOR = 0.3f;
71
72    // The amount of vertical spread between items in the stack [0...1]
73    private static final float PERSPECTIVE_SHIFT_FACTOR = 0.24f;
74
75    // The degree to which the item in the back of the stack is scaled [0...1]
76    // (0 means it's not scaled at all, 1 means it's scaled to nothing)
77    private static final float PERSPECTIVE_SCALE_FACTOR = 0.35f;
78
79    public static Drawable sSharedFolderLeaveBehind = null;
80
81    private ImageView mPreviewBackground;
82    private BubbleTextView mFolderName;
83
84    FolderRingAnimator mFolderRingAnimator = null;
85
86    // These variables are all associated with the drawing of the preview; they are stored
87    // as member variables for shared usage and to avoid computation on each frame
88    private int mIntrinsicIconSize;
89    private float mBaselineIconScale;
90    private int mBaselineIconSize;
91    private int mAvailableSpaceInPreview;
92    private int mTotalWidth = -1;
93    private int mPreviewOffsetX;
94    private int mPreviewOffsetY;
95    private float mMaxPerspectiveShift;
96    boolean mAnimating = false;
97
98    private PreviewItemDrawingParams mParams = new PreviewItemDrawingParams(0, 0, 0, 0);
99    private PreviewItemDrawingParams mAnimParams = new PreviewItemDrawingParams(0, 0, 0, 0);
100    private ArrayList<ShortcutInfo> mHiddenItems = new ArrayList<ShortcutInfo>();
101
102    public FolderIcon(Context context, AttributeSet attrs) {
103        super(context, attrs);
104        init();
105    }
106
107    public FolderIcon(Context context) {
108        super(context);
109        init();
110    }
111
112    private void init() {
113        mLongPressHelper = new CheckLongPressHelper(this);
114    }
115
116    public boolean isDropEnabled() {
117        final ViewGroup cellLayoutChildren = (ViewGroup) getParent();
118        final ViewGroup cellLayout = (ViewGroup) cellLayoutChildren.getParent();
119        final Workspace workspace = (Workspace) cellLayout.getParent();
120        return !workspace.isSmall();
121    }
122
123    static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
124            FolderInfo folderInfo, IconCache iconCache) {
125        @SuppressWarnings("all") // suppress dead code warning
126        final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
127        if (error) {
128            throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
129                    "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
130                    "is dependent on this");
131        }
132
133        FolderIcon icon = (FolderIcon) LayoutInflater.from(launcher).inflate(resId, group, false);
134
135        icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
136        icon.mFolderName.setText(folderInfo.title);
137        icon.mPreviewBackground = (ImageView) icon.findViewById(R.id.preview_background);
138
139        icon.setTag(folderInfo);
140        icon.setOnClickListener(launcher);
141        icon.mInfo = folderInfo;
142        icon.mLauncher = launcher;
143        icon.setContentDescription(String.format(launcher.getString(R.string.folder_name_format),
144                folderInfo.title));
145        Folder folder = Folder.fromXml(launcher);
146        folder.setDragController(launcher.getDragController());
147        folder.setFolderIcon(icon);
148        folder.bind(folderInfo);
149        icon.mFolder = folder;
150
151        icon.mFolderRingAnimator = new FolderRingAnimator(launcher, icon);
152        folderInfo.addListener(icon);
153
154        return icon;
155    }
156
157    @Override
158    protected Parcelable onSaveInstanceState() {
159        sStaticValuesDirty = true;
160        return super.onSaveInstanceState();
161    }
162
163    public static class FolderRingAnimator {
164        public int mCellX;
165        public int mCellY;
166        private CellLayout mCellLayout;
167        public float mOuterRingSize;
168        public float mInnerRingSize;
169        public FolderIcon mFolderIcon = null;
170        public Drawable mOuterRingDrawable = null;
171        public Drawable mInnerRingDrawable = null;
172        public static Drawable sSharedOuterRingDrawable = null;
173        public static Drawable sSharedInnerRingDrawable = null;
174        public static int sPreviewSize = -1;
175        public static int sPreviewPadding = -1;
176
177        private ValueAnimator mAcceptAnimator;
178        private ValueAnimator mNeutralAnimator;
179
180        public FolderRingAnimator(Launcher launcher, FolderIcon folderIcon) {
181            mFolderIcon = folderIcon;
182            Resources res = launcher.getResources();
183            mOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
184            mInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
185
186            // We need to reload the static values when configuration changes in case they are
187            // different in another configuration
188            if (sStaticValuesDirty) {
189                sPreviewSize = res.getDimensionPixelSize(R.dimen.folder_preview_size);
190                sPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding);
191                sSharedOuterRingDrawable = res.getDrawable(R.drawable.portal_ring_outer_holo);
192                sSharedInnerRingDrawable = res.getDrawable(R.drawable.portal_ring_inner_holo);
193                sSharedFolderLeaveBehind = res.getDrawable(R.drawable.portal_ring_rest);
194                sStaticValuesDirty = false;
195            }
196        }
197
198        public void animateToAcceptState() {
199            if (mNeutralAnimator != null) {
200                mNeutralAnimator.cancel();
201            }
202            mAcceptAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
203            mAcceptAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
204
205            final int previewSize = sPreviewSize;
206            mAcceptAnimator.addUpdateListener(new AnimatorUpdateListener() {
207                public void onAnimationUpdate(ValueAnimator animation) {
208                    final float percent = (Float) animation.getAnimatedValue();
209                    mOuterRingSize = (1 + percent * OUTER_RING_GROWTH_FACTOR) * previewSize;
210                    mInnerRingSize = (1 + percent * INNER_RING_GROWTH_FACTOR) * previewSize;
211                    if (mCellLayout != null) {
212                        mCellLayout.invalidate();
213                    }
214                }
215            });
216            mAcceptAnimator.addListener(new AnimatorListenerAdapter() {
217                @Override
218                public void onAnimationStart(Animator animation) {
219                    if (mFolderIcon != null) {
220                        mFolderIcon.mPreviewBackground.setVisibility(INVISIBLE);
221                    }
222                }
223            });
224            mAcceptAnimator.start();
225        }
226
227        public void animateToNaturalState() {
228            if (mAcceptAnimator != null) {
229                mAcceptAnimator.cancel();
230            }
231            mNeutralAnimator = LauncherAnimUtils.ofFloat(mCellLayout, 0f, 1f);
232            mNeutralAnimator.setDuration(CONSUMPTION_ANIMATION_DURATION);
233
234            final int previewSize = sPreviewSize;
235            mNeutralAnimator.addUpdateListener(new AnimatorUpdateListener() {
236                public void onAnimationUpdate(ValueAnimator animation) {
237                    final float percent = (Float) animation.getAnimatedValue();
238                    mOuterRingSize = (1 + (1 - percent) * OUTER_RING_GROWTH_FACTOR) * previewSize;
239                    mInnerRingSize = (1 + (1 - percent) * INNER_RING_GROWTH_FACTOR) * previewSize;
240                    if (mCellLayout != null) {
241                        mCellLayout.invalidate();
242                    }
243                }
244            });
245            mNeutralAnimator.addListener(new AnimatorListenerAdapter() {
246                @Override
247                public void onAnimationEnd(Animator animation) {
248                    if (mCellLayout != null) {
249                        mCellLayout.hideFolderAccept(FolderRingAnimator.this);
250                    }
251                    if (mFolderIcon != null) {
252                        mFolderIcon.mPreviewBackground.setVisibility(VISIBLE);
253                    }
254                }
255            });
256            mNeutralAnimator.start();
257        }
258
259        // Location is expressed in window coordinates
260        public void getCell(int[] loc) {
261            loc[0] = mCellX;
262            loc[1] = mCellY;
263        }
264
265        // Location is expressed in window coordinates
266        public void setCell(int x, int y) {
267            mCellX = x;
268            mCellY = y;
269        }
270
271        public void setCellLayout(CellLayout layout) {
272            mCellLayout = layout;
273        }
274
275        public float getOuterRingSize() {
276            return mOuterRingSize;
277        }
278
279        public float getInnerRingSize() {
280            return mInnerRingSize;
281        }
282    }
283
284    Folder getFolder() {
285        return mFolder;
286    }
287
288    FolderInfo getFolderInfo() {
289        return mInfo;
290    }
291
292    private boolean willAcceptItem(ItemInfo item) {
293        final int itemType = item.itemType;
294        return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
295                itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) &&
296                !mFolder.isFull() && item != mInfo && !mInfo.opened);
297    }
298
299    public boolean acceptDrop(Object dragInfo) {
300        final ItemInfo item = (ItemInfo) dragInfo;
301        return !mFolder.isDestroyed() && willAcceptItem(item);
302    }
303
304    public void addItem(ShortcutInfo item) {
305        mInfo.add(item);
306    }
307
308    public void onDragEnter(Object dragInfo) {
309        if (mFolder.isDestroyed() || !willAcceptItem((ItemInfo) dragInfo)) return;
310        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
311        CellLayout layout = (CellLayout) getParent().getParent();
312        mFolderRingAnimator.setCell(lp.cellX, lp.cellY);
313        mFolderRingAnimator.setCellLayout(layout);
314        mFolderRingAnimator.animateToAcceptState();
315        layout.showFolderAccept(mFolderRingAnimator);
316    }
317
318    public void onDragOver(Object dragInfo) {
319    }
320
321    public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
322            final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
323            float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
324
325        // These correspond two the drawable and view that the icon was dropped _onto_
326        Drawable animateDrawable = ((TextView) destView).getCompoundDrawables()[1];
327        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
328                destView.getMeasuredWidth());
329
330        // This will animate the first item from it's position as an icon into its
331        // position as the first item in the preview
332        animateFirstItem(animateDrawable, INITIAL_ITEM_ANIMATION_DURATION, false, null);
333        addItem(destInfo);
334
335        // This will animate the dragView (srcView) into the new folder
336        onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable, null);
337    }
338
339    public void performDestroyAnimation(final View finalView, Runnable onCompleteRunnable) {
340        Drawable animateDrawable = ((TextView) finalView).getCompoundDrawables()[1];
341        computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
342                finalView.getMeasuredWidth());
343
344        // This will animate the first item from it's position as an icon into its
345        // position as the first item in the preview
346        animateFirstItem(animateDrawable, FINAL_ITEM_ANIMATION_DURATION, true,
347                onCompleteRunnable);
348    }
349
350    public void onDragExit(Object dragInfo) {
351        onDragExit();
352    }
353
354    public void onDragExit() {
355        mFolderRingAnimator.animateToNaturalState();
356    }
357
358    private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
359            float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable,
360            DragObject d) {
361        item.cellX = -1;
362        item.cellY = -1;
363
364        // Typically, the animateView corresponds to the DragView; however, if this is being done
365        // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
366        // will not have a view to animate
367        if (animateView != null) {
368            DragLayer dragLayer = mLauncher.getDragLayer();
369            Rect from = new Rect();
370            dragLayer.getViewRectRelativeToSelf(animateView, from);
371            Rect to = finalRect;
372            if (to == null) {
373                to = new Rect();
374                Workspace workspace = mLauncher.getWorkspace();
375                // Set cellLayout and this to it's final state to compute final animation locations
376                workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
377                float scaleX = getScaleX();
378                float scaleY = getScaleY();
379                setScaleX(1.0f);
380                setScaleY(1.0f);
381                scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
382                // Finished computing final animation locations, restore current state
383                setScaleX(scaleX);
384                setScaleY(scaleY);
385                workspace.resetTransitionTransform((CellLayout) getParent().getParent());
386            }
387
388            int[] center = new int[2];
389            float scale = getLocalCenterForIndex(index, center);
390            center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
391            center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
392
393            to.offset(center[0] - animateView.getMeasuredWidth() / 2,
394                    center[1] - animateView.getMeasuredHeight() / 2);
395
396            float finalAlpha = index < NUM_ITEMS_IN_PREVIEW ? 0.5f : 0f;
397
398            float finalScale = scale * scaleRelativeToDragLayer;
399            dragLayer.animateView(animateView, from, to, finalAlpha,
400                    1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
401                    new DecelerateInterpolator(2), new AccelerateInterpolator(2),
402                    postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
403            addItem(item);
404            mHiddenItems.add(item);
405            mFolder.hideItem(item);
406            postDelayed(new Runnable() {
407                public void run() {
408                    mHiddenItems.remove(item);
409                    mFolder.showItem(item);
410                    invalidate();
411                }
412            }, DROP_IN_ANIMATION_DURATION);
413        } else {
414            addItem(item);
415        }
416    }
417
418    public void onDrop(DragObject d) {
419        ShortcutInfo item;
420        if (d.dragInfo instanceof ApplicationInfo) {
421            // Came from all apps -- make a copy
422            item = ((ApplicationInfo) d.dragInfo).makeShortcut();
423        } else {
424            item = (ShortcutInfo) d.dragInfo;
425        }
426        mFolder.notifyDrop();
427        onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable, d);
428    }
429
430    public DropTarget getDropTargetDelegate(DragObject d) {
431        return null;
432    }
433
434    private void computePreviewDrawingParams(int drawableSize, int totalSize) {
435        if (mIntrinsicIconSize != drawableSize || mTotalWidth != totalSize) {
436            mIntrinsicIconSize = drawableSize;
437            mTotalWidth = totalSize;
438
439            final int previewSize = FolderRingAnimator.sPreviewSize;
440            final int previewPadding = FolderRingAnimator.sPreviewPadding;
441
442            mAvailableSpaceInPreview = (previewSize - 2 * previewPadding);
443            // cos(45) = 0.707  + ~= 0.1) = 0.8f
444            int adjustedAvailableSpace = (int) ((mAvailableSpaceInPreview / 2) * (1 + 0.8f));
445
446            int unscaledHeight = (int) (mIntrinsicIconSize * (1 + PERSPECTIVE_SHIFT_FACTOR));
447            mBaselineIconScale = (1.0f * adjustedAvailableSpace / unscaledHeight);
448
449            mBaselineIconSize = (int) (mIntrinsicIconSize * mBaselineIconScale);
450            mMaxPerspectiveShift = mBaselineIconSize * PERSPECTIVE_SHIFT_FACTOR;
451
452            mPreviewOffsetX = (mTotalWidth - mAvailableSpaceInPreview) / 2;
453            mPreviewOffsetY = previewPadding;
454        }
455    }
456
457    private void computePreviewDrawingParams(Drawable d) {
458        computePreviewDrawingParams(d.getIntrinsicWidth(), getMeasuredWidth());
459    }
460
461    class PreviewItemDrawingParams {
462        PreviewItemDrawingParams(float transX, float transY, float scale, int overlayAlpha) {
463            this.transX = transX;
464            this.transY = transY;
465            this.scale = scale;
466            this.overlayAlpha = overlayAlpha;
467        }
468        float transX;
469        float transY;
470        float scale;
471        int overlayAlpha;
472        Drawable drawable;
473    }
474
475    private float getLocalCenterForIndex(int index, int[] center) {
476        mParams = computePreviewItemDrawingParams(Math.min(NUM_ITEMS_IN_PREVIEW, index), mParams);
477
478        mParams.transX += mPreviewOffsetX;
479        mParams.transY += mPreviewOffsetY;
480        float offsetX = mParams.transX + (mParams.scale * mIntrinsicIconSize) / 2;
481        float offsetY = mParams.transY + (mParams.scale * mIntrinsicIconSize) / 2;
482
483        center[0] = (int) Math.round(offsetX);
484        center[1] = (int) Math.round(offsetY);
485        return mParams.scale;
486    }
487
488    private PreviewItemDrawingParams computePreviewItemDrawingParams(int index,
489            PreviewItemDrawingParams params) {
490        index = NUM_ITEMS_IN_PREVIEW - index - 1;
491        float r = (index * 1.0f) / (NUM_ITEMS_IN_PREVIEW - 1);
492        float scale = (1 - PERSPECTIVE_SCALE_FACTOR * (1 - r));
493
494        float offset = (1 - r) * mMaxPerspectiveShift;
495        float scaledSize = scale * mBaselineIconSize;
496        float scaleOffsetCorrection = (1 - scale) * mBaselineIconSize;
497
498        // We want to imagine our coordinates from the bottom left, growing up and to the
499        // right. This is natural for the x-axis, but for the y-axis, we have to invert things.
500        float transY = mAvailableSpaceInPreview - (offset + scaledSize + scaleOffsetCorrection);
501        float transX = offset + scaleOffsetCorrection;
502        float totalScale = mBaselineIconScale * scale;
503        final int overlayAlpha = (int) (80 * (1 - r));
504
505        if (params == null) {
506            params = new PreviewItemDrawingParams(transX, transY, totalScale, overlayAlpha);
507        } else {
508            params.transX = transX;
509            params.transY = transY;
510            params.scale = totalScale;
511            params.overlayAlpha = overlayAlpha;
512        }
513        return params;
514    }
515
516    private void drawPreviewItem(Canvas canvas, PreviewItemDrawingParams params) {
517        canvas.save();
518        canvas.translate(params.transX + mPreviewOffsetX, params.transY + mPreviewOffsetY);
519        canvas.scale(params.scale, params.scale);
520        Drawable d = params.drawable;
521
522        if (d != null) {
523            d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
524            d.setFilterBitmap(true);
525            d.setColorFilter(Color.argb(params.overlayAlpha, 0, 0, 0), PorterDuff.Mode.SRC_ATOP);
526            d.draw(canvas);
527            d.clearColorFilter();
528            d.setFilterBitmap(false);
529        }
530        canvas.restore();
531    }
532
533    @Override
534    protected void dispatchDraw(Canvas canvas) {
535        super.dispatchDraw(canvas);
536
537        if (mFolder == null) return;
538        if (mFolder.getItemCount() == 0 && !mAnimating) return;
539
540        ArrayList<View> items = mFolder.getItemsInReadingOrder();
541        Drawable d;
542        TextView v;
543
544        // Update our drawing parameters if necessary
545        if (mAnimating) {
546            computePreviewDrawingParams(mAnimParams.drawable);
547        } else {
548            v = (TextView) items.get(0);
549            d = v.getCompoundDrawables()[1];
550            computePreviewDrawingParams(d);
551        }
552
553        int nItemsInPreview = Math.min(items.size(), NUM_ITEMS_IN_PREVIEW);
554        if (!mAnimating) {
555            for (int i = nItemsInPreview - 1; i >= 0; i--) {
556                v = (TextView) items.get(i);
557                if (!mHiddenItems.contains(v.getTag())) {
558                    d = v.getCompoundDrawables()[1];
559                    mParams = computePreviewItemDrawingParams(i, mParams);
560                    mParams.drawable = d;
561                    drawPreviewItem(canvas, mParams);
562                }
563            }
564        } else {
565            drawPreviewItem(canvas, mAnimParams);
566        }
567    }
568
569    private void animateFirstItem(final Drawable d, int duration, final boolean reverse,
570            final Runnable onCompleteRunnable) {
571        final PreviewItemDrawingParams finalParams = computePreviewItemDrawingParams(0, null);
572
573        final float scale0 = 1.0f;
574        final float transX0 = (mAvailableSpaceInPreview - d.getIntrinsicWidth()) / 2;
575        final float transY0 = (mAvailableSpaceInPreview - d.getIntrinsicHeight()) / 2;
576        mAnimParams.drawable = d;
577
578        ValueAnimator va = LauncherAnimUtils.ofFloat(this, 0f, 1.0f);
579        va.addUpdateListener(new AnimatorUpdateListener(){
580            public void onAnimationUpdate(ValueAnimator animation) {
581                float progress = (Float) animation.getAnimatedValue();
582                if (reverse) {
583                    progress = 1 - progress;
584                    mPreviewBackground.setAlpha(progress);
585                }
586
587                mAnimParams.transX = transX0 + progress * (finalParams.transX - transX0);
588                mAnimParams.transY = transY0 + progress * (finalParams.transY - transY0);
589                mAnimParams.scale = scale0 + progress * (finalParams.scale - scale0);
590                invalidate();
591            }
592        });
593        va.addListener(new AnimatorListenerAdapter() {
594            @Override
595            public void onAnimationStart(Animator animation) {
596                mAnimating = true;
597            }
598            @Override
599            public void onAnimationEnd(Animator animation) {
600                mAnimating = false;
601                if (onCompleteRunnable != null) {
602                    onCompleteRunnable.run();
603                }
604            }
605        });
606        va.setDuration(duration);
607        va.start();
608    }
609
610    public void setTextVisible(boolean visible) {
611        if (visible) {
612            mFolderName.setVisibility(VISIBLE);
613        } else {
614            mFolderName.setVisibility(INVISIBLE);
615        }
616    }
617
618    public boolean getTextVisible() {
619        return mFolderName.getVisibility() == VISIBLE;
620    }
621
622    public void onItemsChanged() {
623        invalidate();
624        requestLayout();
625    }
626
627    public void onAdd(ShortcutInfo item) {
628        invalidate();
629        requestLayout();
630    }
631
632    public void onRemove(ShortcutInfo item) {
633        invalidate();
634        requestLayout();
635    }
636
637    public void onTitleChanged(CharSequence title) {
638        mFolderName.setText(title.toString());
639        setContentDescription(String.format(getContext().getString(R.string.folder_name_format),
640                title));
641    }
642
643    @Override
644    public boolean onTouchEvent(MotionEvent event) {
645        // Call the superclass onTouchEvent first, because sometimes it changes the state to
646        // isPressed() on an ACTION_UP
647        boolean result = super.onTouchEvent(event);
648
649        switch (event.getAction()) {
650            case MotionEvent.ACTION_DOWN:
651                mLongPressHelper.postCheckForLongPress();
652                break;
653            case MotionEvent.ACTION_CANCEL:
654            case MotionEvent.ACTION_UP:
655                mLongPressHelper.cancelLongPress();
656                break;
657        }
658        return result;
659    }
660
661    @Override
662    public void cancelLongPress() {
663        super.cancelLongPress();
664
665        mLongPressHelper.cancelLongPress();
666    }
667}
668