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