Home | History | Annotate | Download | only in folder
      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.ObjectAnimator;
     22 import android.content.Context;
     23 import android.graphics.Canvas;
     24 import android.graphics.Point;
     25 import android.graphics.Rect;
     26 import android.graphics.Region;
     27 import android.graphics.drawable.Drawable;
     28 import android.os.Parcelable;
     29 import android.support.annotation.NonNull;
     30 import android.util.AttributeSet;
     31 import android.util.Property;
     32 import android.view.LayoutInflater;
     33 import android.view.MotionEvent;
     34 import android.view.View;
     35 import android.view.ViewConfiguration;
     36 import android.view.ViewGroup;
     37 import android.view.animation.AccelerateInterpolator;
     38 import android.view.animation.DecelerateInterpolator;
     39 import android.widget.FrameLayout;
     40 
     41 import com.android.launcher3.Alarm;
     42 import com.android.launcher3.AppInfo;
     43 import com.android.launcher3.BubbleTextView;
     44 import com.android.launcher3.CellLayout;
     45 import com.android.launcher3.CheckLongPressHelper;
     46 import com.android.launcher3.DeviceProfile;
     47 import com.android.launcher3.DropTarget.DragObject;
     48 import com.android.launcher3.FolderInfo;
     49 import com.android.launcher3.FolderInfo.FolderListener;
     50 import com.android.launcher3.ItemInfo;
     51 import com.android.launcher3.Launcher;
     52 import com.android.launcher3.LauncherAnimUtils;
     53 import com.android.launcher3.LauncherSettings;
     54 import com.android.launcher3.OnAlarmListener;
     55 import com.android.launcher3.R;
     56 import com.android.launcher3.ShortcutInfo;
     57 import com.android.launcher3.SimpleOnStylusPressListener;
     58 import com.android.launcher3.StylusEventHelper;
     59 import com.android.launcher3.Utilities;
     60 import com.android.launcher3.Workspace;
     61 import com.android.launcher3.badge.BadgeRenderer;
     62 import com.android.launcher3.badge.FolderBadgeInfo;
     63 import com.android.launcher3.config.FeatureFlags;
     64 import com.android.launcher3.dragndrop.BaseItemDragListener;
     65 import com.android.launcher3.dragndrop.DragLayer;
     66 import com.android.launcher3.dragndrop.DragView;
     67 import com.android.launcher3.graphics.IconPalette;
     68 import com.android.launcher3.util.Thunk;
     69 import com.android.launcher3.widget.PendingAddShortcutInfo;
     70 
     71 import java.util.ArrayList;
     72 import java.util.List;
     73 
     74 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
     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     static final int DROP_IN_ANIMATION_DURATION = 400;
     93 
     94     // Flag whether the folder should open itself when an item is dragged over is enabled.
     95     public static final boolean SPRING_LOADING_ENABLED = true;
     96 
     97     // Delay when drag enters until the folder opens, in miliseconds.
     98     private static final int ON_OPEN_DELAY = 800;
     99 
    100     @Thunk BubbleTextView mFolderName;
    101 
    102     PreviewBackground mBackground = new PreviewBackground();
    103     private boolean mBackgroundIsVisible = true;
    104 
    105     FolderIconPreviewVerifier mPreviewVerifier;
    106     PreviewLayoutRule mPreviewLayoutRule;
    107     private PreviewItemManager mPreviewItemManager;
    108     private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0, 0);
    109 
    110     boolean mAnimating = false;
    111     private Rect mTempBounds = new Rect();
    112 
    113     private float mSlop;
    114 
    115     private Alarm mOpenAlarm = new Alarm();
    116 
    117     private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
    118     private BadgeRenderer mBadgeRenderer;
    119     private float mBadgeScale;
    120     private Point mTempSpaceForBadgeOffset = new Point();
    121 
    122     private static final Property<FolderIcon, Float> BADGE_SCALE_PROPERTY
    123             = new Property<FolderIcon, Float>(Float.TYPE, "badgeScale") {
    124         @Override
    125         public Float get(FolderIcon folderIcon) {
    126             return folderIcon.mBadgeScale;
    127         }
    128 
    129         @Override
    130         public void set(FolderIcon folderIcon, Float value) {
    131             folderIcon.mBadgeScale = value;
    132             folderIcon.invalidate();
    133         }
    134     };
    135 
    136     public FolderIcon(Context context, AttributeSet attrs) {
    137         super(context, attrs);
    138         init();
    139     }
    140 
    141     public FolderIcon(Context context) {
    142         super(context);
    143         init();
    144     }
    145 
    146     private void init() {
    147         mLongPressHelper = new CheckLongPressHelper(this);
    148         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
    149         mPreviewLayoutRule = FeatureFlags.LAUNCHER3_LEGACY_FOLDER_ICON ?
    150                 new StackFolderIconLayoutRule() :
    151                 new ClippedFolderIconLayoutRule();
    152         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    153         mPreviewItemManager = new PreviewItemManager(this);
    154     }
    155 
    156     public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
    157             FolderInfo folderInfo) {
    158         @SuppressWarnings("all") // suppress dead code warning
    159         final boolean error = INITIAL_ITEM_ANIMATION_DURATION >= DROP_IN_ANIMATION_DURATION;
    160         if (error) {
    161             throw new IllegalStateException("DROP_IN_ANIMATION_DURATION must be greater than " +
    162                     "INITIAL_ITEM_ANIMATION_DURATION, as sequencing of adding first two items " +
    163                     "is dependent on this");
    164         }
    165 
    166         DeviceProfile grid = launcher.getDeviceProfile();
    167         FolderIcon icon = (FolderIcon) LayoutInflater.from(group.getContext())
    168                 .inflate(resId, group, false);
    169 
    170         icon.setClipToPadding(false);
    171         icon.mFolderName = (BubbleTextView) icon.findViewById(R.id.folder_icon_name);
    172         icon.mFolderName.setText(folderInfo.title);
    173         icon.mFolderName.setCompoundDrawablePadding(0);
    174         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
    175         lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
    176 
    177         icon.setTag(folderInfo);
    178         icon.setOnClickListener(launcher);
    179         icon.mInfo = folderInfo;
    180         icon.mLauncher = launcher;
    181         icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
    182         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
    183         Folder folder = Folder.fromXml(launcher);
    184         folder.setDragController(launcher.getDragController());
    185         folder.setFolderIcon(icon);
    186         folder.bind(folderInfo);
    187         icon.setFolder(folder);
    188         icon.setAccessibilityDelegate(launcher.getAccessibilityDelegate());
    189 
    190         folderInfo.addListener(icon);
    191 
    192         icon.setOnFocusChangeListener(launcher.mFocusHandler);
    193         return icon;
    194     }
    195 
    196     @Override
    197     protected Parcelable onSaveInstanceState() {
    198         sStaticValuesDirty = true;
    199         return super.onSaveInstanceState();
    200     }
    201 
    202     public Folder getFolder() {
    203         return mFolder;
    204     }
    205 
    206     private void setFolder(Folder folder) {
    207         mFolder = folder;
    208         mPreviewVerifier = new FolderIconPreviewVerifier(mLauncher.getDeviceProfile().inv);
    209         mPreviewItemManager.updateItemDrawingParams(false);
    210     }
    211 
    212     private boolean willAcceptItem(ItemInfo item) {
    213         final int itemType = item.itemType;
    214         return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
    215                 itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT ||
    216                 itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
    217                 !mFolder.isFull() && item != mInfo && !mFolder.isOpen());
    218     }
    219 
    220     public boolean acceptDrop(ItemInfo dragInfo) {
    221         return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
    222     }
    223 
    224     public void addItem(ShortcutInfo item) {
    225         addItem(item, true);
    226     }
    227 
    228     public void addItem(ShortcutInfo item, boolean animate) {
    229         mInfo.add(item, animate);
    230     }
    231 
    232     public void removeItem(ShortcutInfo item, boolean animate) {
    233         mInfo.remove(item, animate);
    234     }
    235 
    236     public void onDragEnter(ItemInfo dragInfo) {
    237         if (mFolder.isDestroyed() || !willAcceptItem(dragInfo)) return;
    238         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
    239         CellLayout cl = (CellLayout) getParent().getParent();
    240 
    241         mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
    242         mOpenAlarm.setOnAlarmListener(mOnOpenListener);
    243         if (SPRING_LOADING_ENABLED &&
    244                 ((dragInfo instanceof AppInfo)
    245                         || (dragInfo instanceof ShortcutInfo)
    246                         || (dragInfo instanceof PendingAddShortcutInfo))) {
    247             mOpenAlarm.setAlarm(ON_OPEN_DELAY);
    248         }
    249     }
    250 
    251     OnAlarmListener mOnOpenListener = new OnAlarmListener() {
    252         public void onAlarm(Alarm alarm) {
    253             mFolder.beginExternalDrag();
    254             mFolder.animateOpen();
    255         }
    256     };
    257 
    258     public Drawable prepareCreateAnimation(final View destView) {
    259         return mPreviewItemManager.prepareCreateAnimation(destView);
    260     }
    261 
    262     public void performCreateAnimation(final ShortcutInfo destInfo, final View destView,
    263             final ShortcutInfo srcInfo, final DragView srcView, Rect dstRect,
    264             float scaleRelativeToDragLayer, Runnable postAnimationRunnable) {
    265         prepareCreateAnimation(destView);
    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         mPreviewItemManager.createFirstItemAnimation(false /* reverse */, null)
    270                 .start();
    271 
    272         // This will animate the dragView (srcView) into the new folder
    273         onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1, postAnimationRunnable);
    274     }
    275 
    276     public void performDestroyAnimation(Runnable onCompleteRunnable) {
    277         // This will animate the final item in the preview to be full size.
    278         mPreviewItemManager.createFirstItemAnimation(true /* reverse */, onCompleteRunnable)
    279                 .start();
    280     }
    281 
    282     public void onDragExit() {
    283         mBackground.animateToRest();
    284         mOpenAlarm.cancelAlarm();
    285     }
    286 
    287     private void onDrop(final ShortcutInfo item, DragView animateView, Rect finalRect,
    288             float scaleRelativeToDragLayer, int index, Runnable postAnimationRunnable) {
    289         item.cellX = -1;
    290         item.cellY = -1;
    291 
    292         // Typically, the animateView corresponds to the DragView; however, if this is being done
    293         // after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
    294         // will not have a view to animate
    295         if (animateView != null) {
    296             DragLayer dragLayer = mLauncher.getDragLayer();
    297             Rect from = new Rect();
    298             dragLayer.getViewRectRelativeToSelf(animateView, from);
    299             Rect to = finalRect;
    300             if (to == null) {
    301                 to = new Rect();
    302                 Workspace workspace = mLauncher.getWorkspace();
    303                 // Set cellLayout and this to it's final state to compute final animation locations
    304                 workspace.setFinalTransitionTransform((CellLayout) getParent().getParent());
    305                 float scaleX = getScaleX();
    306                 float scaleY = getScaleY();
    307                 setScaleX(1.0f);
    308                 setScaleY(1.0f);
    309                 scaleRelativeToDragLayer = dragLayer.getDescendantRectRelativeToSelf(this, to);
    310                 // Finished computing final animation locations, restore current state
    311                 setScaleX(scaleX);
    312                 setScaleY(scaleY);
    313                 workspace.resetTransitionTransform((CellLayout) getParent().getParent());
    314             }
    315 
    316             boolean itemAdded = false;
    317             if (index >= mPreviewLayoutRule.maxNumItems()
    318                     && mPreviewLayoutRule.hasEnterExitIndices()) {
    319                 List<BubbleTextView> oldPreviewItems = getPreviewItemsOnPage(0);
    320                 addItem(item, false);
    321                 List<BubbleTextView> newPreviewItems = getPreviewItemsOnPage(0);
    322 
    323                 if (!oldPreviewItems.containsAll(newPreviewItems)) {
    324                     for (int i = 0; i < newPreviewItems.size(); ++i) {
    325                         if (newPreviewItems.get(i).getTag().equals(item)) {
    326                             // If the item dropped is going to be in the preview, we update the
    327                             // index here to reflect its position in the preview.
    328                             index = i;
    329                         }
    330                     }
    331                     mPreviewItemManager.onDrop(oldPreviewItems, newPreviewItems, item);
    332                     itemAdded = true;
    333                 } else {
    334                     removeItem(item, false);
    335                 }
    336             }
    337 
    338             if (!itemAdded) {
    339                 addItem(item);
    340             }
    341 
    342             int[] center = new int[2];
    343             float scale = getLocalCenterForIndex(index, index + 1, center);
    344             center[0] = (int) Math.round(scaleRelativeToDragLayer * center[0]);
    345             center[1] = (int) Math.round(scaleRelativeToDragLayer * center[1]);
    346 
    347             to.offset(center[0] - animateView.getMeasuredWidth() / 2,
    348                     center[1] - animateView.getMeasuredHeight() / 2);
    349 
    350             float finalAlpha = index < mPreviewLayoutRule.maxNumItems() ? 0.5f : 0f;
    351 
    352             float finalScale = scale * scaleRelativeToDragLayer;
    353             dragLayer.animateView(animateView, from, to, finalAlpha,
    354                     1, 1, finalScale, finalScale, DROP_IN_ANIMATION_DURATION,
    355                     new DecelerateInterpolator(2), new AccelerateInterpolator(2),
    356                     postAnimationRunnable, DragLayer.ANIMATION_END_DISAPPEAR, null);
    357 
    358             mFolder.hideItem(item);
    359 
    360             if (!itemAdded) mPreviewItemManager.hidePreviewItem(index, true);
    361             final int finalIndex = index;
    362             postDelayed(new Runnable() {
    363                 public void run() {
    364                     mPreviewItemManager.hidePreviewItem(finalIndex, false);
    365                     mFolder.showItem(item);
    366                     invalidate();
    367                 }
    368             }, DROP_IN_ANIMATION_DURATION);
    369         } else {
    370             addItem(item);
    371         }
    372     }
    373 
    374     public void onDrop(DragObject d) {
    375         ShortcutInfo item;
    376         if (d.dragInfo instanceof AppInfo) {
    377             // Came from all apps -- make a copy
    378             item = ((AppInfo) d.dragInfo).makeShortcut();
    379         } else if (d.dragSource instanceof BaseItemDragListener){
    380             // Came from a different window -- make a copy
    381             item = new ShortcutInfo((ShortcutInfo) d.dragInfo);
    382         } else {
    383             item = (ShortcutInfo) d.dragInfo;
    384         }
    385         mFolder.notifyDrop();
    386         onDrop(item, d.dragView, null, 1.0f, mInfo.contents.size(), d.postAnimationRunnable);
    387     }
    388 
    389     public void setBadgeInfo(FolderBadgeInfo badgeInfo) {
    390         updateBadgeScale(mBadgeInfo.hasBadge(), badgeInfo.hasBadge());
    391         mBadgeInfo = badgeInfo;
    392     }
    393 
    394     public PreviewLayoutRule getLayoutRule() {
    395         return mPreviewLayoutRule;
    396     }
    397 
    398     /**
    399      * Sets mBadgeScale to 1 or 0, animating if wasBadged or isBadged is false
    400      * (the badge is being added or removed).
    401      */
    402     private void updateBadgeScale(boolean wasBadged, boolean isBadged) {
    403         float newBadgeScale = isBadged ? 1f : 0f;
    404         // Animate when a badge is first added or when it is removed.
    405         if ((wasBadged ^ isBadged) && isShown()) {
    406             createBadgeScaleAnimator(newBadgeScale).start();
    407         } else {
    408             mBadgeScale = newBadgeScale;
    409             invalidate();
    410         }
    411     }
    412 
    413     public Animator createBadgeScaleAnimator(float... badgeScales) {
    414         return ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, badgeScales);
    415     }
    416 
    417     public boolean hasBadge() {
    418         return mBadgeInfo != null && mBadgeInfo.hasBadge();
    419     }
    420 
    421     private float getLocalCenterForIndex(int index, int curNumItems, int[] center) {
    422         mTmpParams = mPreviewItemManager.computePreviewItemDrawingParams(
    423                 Math.min(mPreviewLayoutRule.maxNumItems(), index), curNumItems, mTmpParams);
    424 
    425         mTmpParams.transX += mBackground.basePreviewOffsetX;
    426         mTmpParams.transY += mBackground.basePreviewOffsetY;
    427 
    428         float intrinsicIconSize = mPreviewItemManager.getIntrinsicIconSize();
    429         float offsetX = mTmpParams.transX + (mTmpParams.scale * intrinsicIconSize) / 2;
    430         float offsetY = mTmpParams.transY + (mTmpParams.scale * intrinsicIconSize) / 2;
    431 
    432         center[0] = Math.round(offsetX);
    433         center[1] = Math.round(offsetY);
    434         return mTmpParams.scale;
    435     }
    436 
    437     public void setFolderBackground(PreviewBackground bg) {
    438         mBackground = bg;
    439         mBackground.setInvalidateDelegate(this);
    440     }
    441 
    442     public void setBackgroundVisible(boolean visible) {
    443         mBackgroundIsVisible = visible;
    444         invalidate();
    445     }
    446 
    447     public PreviewBackground getFolderBackground() {
    448         return mBackground;
    449     }
    450 
    451     public PreviewItemManager getPreviewItemManager() {
    452         return mPreviewItemManager;
    453     }
    454 
    455     @Override
    456     protected void dispatchDraw(Canvas canvas) {
    457         super.dispatchDraw(canvas);
    458 
    459         if (!mBackgroundIsVisible) return;
    460 
    461         mPreviewItemManager.recomputePreviewDrawingParams();
    462 
    463         if (!mBackground.drawingDelegated()) {
    464             mBackground.drawBackground(canvas);
    465         }
    466 
    467         if (mFolder == null) return;
    468         if (mFolder.getItemCount() == 0 && !mAnimating) return;
    469 
    470         final int saveCount;
    471 
    472         if (canvas.isHardwareAccelerated()) {
    473             saveCount = canvas.saveLayer(0, 0, getWidth(), getHeight(), null,
    474                     Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
    475         } else {
    476             saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
    477             if (mPreviewLayoutRule.clipToBackground()) {
    478                 canvas.clipPath(mBackground.getClipPath(), Region.Op.INTERSECT);
    479             }
    480         }
    481 
    482         mPreviewItemManager.draw(canvas);
    483 
    484         if (mPreviewLayoutRule.clipToBackground() && canvas.isHardwareAccelerated()) {
    485             mBackground.clipCanvasHardware(canvas);
    486         }
    487         canvas.restoreToCount(saveCount);
    488 
    489         if (mPreviewLayoutRule.clipToBackground() && !mBackground.drawingDelegated()) {
    490             mBackground.drawBackgroundStroke(canvas);
    491         }
    492 
    493         drawBadge(canvas);
    494     }
    495 
    496     public void drawBadge(Canvas canvas) {
    497         if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
    498             int offsetX = mBackground.getOffsetX();
    499             int offsetY = mBackground.getOffsetY();
    500             int previewSize = (int) (mBackground.previewSize * mBackground.mScale);
    501             mTempBounds.set(offsetX, offsetY, offsetX + previewSize, offsetY + previewSize);
    502 
    503             // If we are animating to the accepting state, animate the badge out.
    504             float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
    505             mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
    506             IconPalette badgePalette = IconPalette.getFolderBadgePalette(getResources());
    507             mBadgeRenderer.draw(canvas, badgePalette, mBadgeInfo, mTempBounds,
    508                     badgeScale, mTempSpaceForBadgeOffset);
    509         }
    510     }
    511 
    512     public void setTextVisible(boolean visible) {
    513         if (visible) {
    514             mFolderName.setVisibility(VISIBLE);
    515         } else {
    516             mFolderName.setVisibility(INVISIBLE);
    517         }
    518     }
    519 
    520     public boolean getTextVisible() {
    521         return mFolderName.getVisibility() == VISIBLE;
    522     }
    523 
    524     /**
    525      * Returns the list of preview items displayed in the icon.
    526      */
    527     public List<BubbleTextView> getPreviewItems() {
    528         return getPreviewItemsOnPage(0);
    529     }
    530 
    531     /**
    532      * Returns the list of "preview items" on {@param page}.
    533      */
    534     public List<BubbleTextView> getPreviewItemsOnPage(int page) {
    535         mPreviewVerifier.setFolderInfo(mFolder.getInfo());
    536 
    537         List<BubbleTextView> itemsToDisplay = new ArrayList<>();
    538         List<BubbleTextView> itemsOnPage = mFolder.getItemsOnPage(page);
    539         int numItems = itemsOnPage.size();
    540         for (int rank = 0; rank < numItems; ++rank) {
    541             if (mPreviewVerifier.isItemInPreview(page, rank)) {
    542                 itemsToDisplay.add(itemsOnPage.get(rank));
    543             }
    544 
    545             if (itemsToDisplay.size() == FolderIcon.NUM_ITEMS_IN_PREVIEW) {
    546                 break;
    547             }
    548         }
    549         return itemsToDisplay;
    550     }
    551 
    552     @Override
    553     protected boolean verifyDrawable(@NonNull Drawable who) {
    554         return mPreviewItemManager.verifyDrawable(who) || super.verifyDrawable(who);
    555     }
    556 
    557     @Override
    558     public void onItemsChanged(boolean animate) {
    559         mPreviewItemManager.updateItemDrawingParams(animate);
    560         invalidate();
    561         requestLayout();
    562     }
    563 
    564     @Override
    565     public void prepareAutoUpdate() {
    566     }
    567 
    568     @Override
    569     public void onAdd(ShortcutInfo item, int rank) {
    570         boolean wasBadged = mBadgeInfo.hasBadge();
    571         mBadgeInfo.addBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
    572         boolean isBadged = mBadgeInfo.hasBadge();
    573         updateBadgeScale(wasBadged, isBadged);
    574         invalidate();
    575         requestLayout();
    576     }
    577 
    578     @Override
    579     public void onRemove(ShortcutInfo item) {
    580         boolean wasBadged = mBadgeInfo.hasBadge();
    581         mBadgeInfo.subtractBadgeInfo(mLauncher.getPopupDataProvider().getBadgeInfoForItem(item));
    582         boolean isBadged = mBadgeInfo.hasBadge();
    583         updateBadgeScale(wasBadged, isBadged);
    584         invalidate();
    585         requestLayout();
    586     }
    587 
    588     @Override
    589     public void onTitleChanged(CharSequence title) {
    590         mFolderName.setText(title);
    591         setContentDescription(getContext().getString(R.string.folder_name_format, title));
    592     }
    593 
    594     @Override
    595     public boolean onTouchEvent(MotionEvent event) {
    596         // Call the superclass onTouchEvent first, because sometimes it changes the state to
    597         // isPressed() on an ACTION_UP
    598         boolean result = super.onTouchEvent(event);
    599 
    600         // Check for a stylus button press, if it occurs cancel any long press checks.
    601         if (mStylusEventHelper.onMotionEvent(event)) {
    602             mLongPressHelper.cancelLongPress();
    603             return true;
    604         }
    605 
    606         switch (event.getAction()) {
    607             case MotionEvent.ACTION_DOWN:
    608                 mLongPressHelper.postCheckForLongPress();
    609                 break;
    610             case MotionEvent.ACTION_CANCEL:
    611             case MotionEvent.ACTION_UP:
    612                 mLongPressHelper.cancelLongPress();
    613                 break;
    614             case MotionEvent.ACTION_MOVE:
    615                 if (!Utilities.pointInView(this, event.getX(), event.getY(), mSlop)) {
    616                     mLongPressHelper.cancelLongPress();
    617                 }
    618                 break;
    619         }
    620         return result;
    621     }
    622 
    623     @Override
    624     public void cancelLongPress() {
    625         super.cancelLongPress();
    626         mLongPressHelper.cancelLongPress();
    627     }
    628 
    629     public void removeListeners() {
    630         mInfo.removeListener(this);
    631         mInfo.removeListener(mFolder);
    632     }
    633 
    634     public void shrinkAndFadeIn(boolean animate) {
    635         // We remove and re-draw the FolderIcon in-case it has changed
    636         final PreviewImageView previewImage = PreviewImageView.get(getContext());
    637         previewImage.removeFromParent();
    638         copyToPreview(previewImage);
    639 
    640         clearLeaveBehindIfExists();
    641 
    642         ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 1, 1, 1);
    643         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
    644         oa.addListener(new AnimatorListenerAdapter() {
    645             @Override
    646             public void onAnimationEnd(Animator animation) {
    647                 // Remove the ImageView copy of the FolderIcon and make the original visible.
    648                 previewImage.removeFromParent();
    649                 setVisibility(View.VISIBLE);
    650             }
    651         });
    652         oa.start();
    653         if (!animate) {
    654             oa.end();
    655         }
    656     }
    657 
    658     public void clearLeaveBehindIfExists() {
    659         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
    660         if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    661             CellLayout cl = (CellLayout) getParent().getParent();
    662             cl.clearFolderLeaveBehind();
    663         }
    664     }
    665 
    666     public void drawLeaveBehindIfExists() {
    667         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
    668         // While the folder is open, the position of the icon cannot change.
    669         lp.canReorder = false;
    670         if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    671             CellLayout cl = (CellLayout) getParent().getParent();
    672             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
    673         }
    674     }
    675 
    676     public void growAndFadeOut() {
    677         drawLeaveBehindIfExists();
    678 
    679         // Push an ImageView copy of the FolderIcon into the DragLayer and hide the original
    680         PreviewImageView previewImage = PreviewImageView.get(getContext());
    681         copyToPreview(previewImage);
    682         setVisibility(View.INVISIBLE);
    683 
    684         ObjectAnimator oa = LauncherAnimUtils.ofViewAlphaAndScale(previewImage, 0, 1.5f, 1.5f);
    685         oa.setDuration(getResources().getInteger(R.integer.config_folderExpandDuration));
    686         oa.start();
    687     }
    688 
    689     /**
    690      * This method draws the FolderIcon to an ImageView and then adds and positions that ImageView
    691      * in the DragLayer in the exact absolute location of the original FolderIcon.
    692      */
    693     private void copyToPreview(PreviewImageView previewImageView) {
    694         previewImageView.copy(this);
    695         if (mFolder != null) {
    696             previewImageView.setPivotX(mFolder.getPivotXForIconAnimation());
    697             previewImageView.setPivotY(mFolder.getPivotYForIconAnimation());
    698             mFolder.bringToFront();
    699         }
    700     }
    701 
    702     public void onFolderClose(int currentPage) {
    703         mPreviewItemManager.onFolderClose(currentPage);
    704     }
    705 
    706     interface PreviewLayoutRule {
    707         PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
    708                 PreviewItemDrawingParams params);
    709         void init(int availableSpace, float intrinsicIconSize, boolean rtl);
    710         float scaleForItem(int index, int totalNumItems);
    711         float getIconSize();
    712         int maxNumItems();
    713         boolean clipToBackground();
    714 
    715         boolean hasEnterExitIndices();
    716         int getExitIndex();
    717         int getEnterIndex();
    718 
    719     }
    720 }
    721