Home | History | Annotate | Download | only in shortcuts
      1 /*
      2  * Copyright (C) 2016 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.shortcuts;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.TimeInterpolator;
     23 import android.annotation.TargetApi;
     24 import android.content.ComponentName;
     25 import android.content.Context;
     26 import android.content.res.Resources;
     27 import android.graphics.Bitmap;
     28 import android.graphics.Color;
     29 import android.graphics.Point;
     30 import android.graphics.Rect;
     31 import android.graphics.drawable.ShapeDrawable;
     32 import android.os.Build;
     33 import android.os.Handler;
     34 import android.os.Looper;
     35 import android.util.AttributeSet;
     36 import android.view.Gravity;
     37 import android.view.LayoutInflater;
     38 import android.view.MotionEvent;
     39 import android.view.View;
     40 import android.view.accessibility.AccessibilityEvent;
     41 import android.view.animation.DecelerateInterpolator;
     42 import android.widget.LinearLayout;
     43 
     44 import com.android.launcher3.BubbleTextView;
     45 import com.android.launcher3.DragSource;
     46 import com.android.launcher3.DropTarget;
     47 import com.android.launcher3.IconCache;
     48 import com.android.launcher3.ItemInfo;
     49 import com.android.launcher3.Launcher;
     50 import com.android.launcher3.LauncherAnimUtils;
     51 import com.android.launcher3.LauncherAppState;
     52 import com.android.launcher3.LauncherModel;
     53 import com.android.launcher3.LauncherSettings;
     54 import com.android.launcher3.LauncherViewPropertyAnimator;
     55 import com.android.launcher3.LogAccelerateInterpolator;
     56 import com.android.launcher3.R;
     57 import com.android.launcher3.ShortcutInfo;
     58 import com.android.launcher3.Utilities;
     59 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
     60 import com.android.launcher3.compat.UserHandleCompat;
     61 import com.android.launcher3.dragndrop.DragController;
     62 import com.android.launcher3.dragndrop.DragLayer;
     63 import com.android.launcher3.dragndrop.DragOptions;
     64 import com.android.launcher3.dragndrop.DragView;
     65 import com.android.launcher3.graphics.TriangleShape;
     66 import com.android.launcher3.userevent.nano.LauncherLogProto;
     67 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
     68 
     69 import java.util.Collections;
     70 import java.util.List;
     71 
     72 /**
     73  * A container for shortcuts to deep links within apps.
     74  */
     75 @TargetApi(Build.VERSION_CODES.N)
     76 public class DeepShortcutsContainer extends LinearLayout implements View.OnLongClickListener,
     77         View.OnTouchListener, DragSource, DragController.DragListener {
     78     private static final String TAG = "ShortcutsContainer";
     79 
     80     private final Point mIconShift = new Point();
     81 
     82     private final Launcher mLauncher;
     83     private final DeepShortcutManager mDeepShortcutsManager;
     84     private final int mStartDragThreshold;
     85     private final ShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
     86     private final boolean mIsRtl;
     87 
     88     private BubbleTextView mDeferredDragIcon;
     89     private final Rect mTempRect = new Rect();
     90     private Point mIconLastTouchPos = new Point();
     91     private boolean mIsLeftAligned;
     92     private boolean mIsAboveIcon;
     93     private View mArrow;
     94 
     95     private Animator mOpenCloseAnimator;
     96     private boolean mDeferContainerRemoval;
     97     private boolean mIsOpen;
     98 
     99     public DeepShortcutsContainer(Context context, AttributeSet attrs, int defStyleAttr) {
    100         super(context, attrs, defStyleAttr);
    101         mLauncher = Launcher.getLauncher(context);
    102         mDeepShortcutsManager = LauncherAppState.getInstance().getShortcutManager();
    103 
    104         mStartDragThreshold = getResources().getDimensionPixelSize(
    105                 R.dimen.deep_shortcuts_start_drag_threshold);
    106         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
    107         mIsRtl = Utilities.isRtl(getResources());
    108     }
    109 
    110     public DeepShortcutsContainer(Context context, AttributeSet attrs) {
    111         this(context, attrs, 0);
    112     }
    113 
    114     public DeepShortcutsContainer(Context context) {
    115         this(context, null, 0);
    116     }
    117 
    118     public void populateAndShow(final BubbleTextView originalIcon, final List<String> ids) {
    119         final Resources resources = getResources();
    120         final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
    121         final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
    122         final int arrowHorizontalOffset = resources.getDimensionPixelSize(
    123                 R.dimen.deep_shortcuts_arrow_horizontal_offset);
    124         final int arrowVerticalOffset = resources.getDimensionPixelSize(
    125                 R.dimen.deep_shortcuts_arrow_vertical_offset);
    126 
    127         // Add dummy views first, and populate with real shortcut info when ready.
    128         final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
    129         final LayoutInflater inflater = mLauncher.getLayoutInflater();
    130         int numShortcuts = Math.min(ids.size(), ShortcutFilter.MAX_SHORTCUTS);
    131         for (int i = 0; i < numShortcuts; i++) {
    132             final DeepShortcutView shortcut =
    133                     (DeepShortcutView) inflater.inflate(R.layout.deep_shortcut, this, false);
    134             if (i < numShortcuts - 1) {
    135                 ((LayoutParams) shortcut.getLayoutParams()).bottomMargin = spacing;
    136             }
    137             shortcut.getBubbleText().setAccessibilityDelegate(mAccessibilityDelegate);
    138             addView(shortcut);
    139         }
    140         setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
    141                 numShortcuts, originalIcon.getContentDescription().toString()));
    142 
    143         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
    144         orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
    145 
    146         // Add the arrow.
    147         mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
    148         mArrow.setPivotX(arrowWidth / 2);
    149         mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
    150 
    151         animateOpen();
    152 
    153         deferDrag(originalIcon);
    154 
    155         // Load the shortcuts on a background thread and update the container as it animates.
    156         final Looper workerLooper = LauncherModel.getWorkerLooper();
    157         final Handler uiHandler = new Handler(Looper.getMainLooper());
    158         final ItemInfo originalInfo = (ItemInfo) originalIcon.getTag();
    159         final UserHandleCompat user = originalInfo.user;
    160         final ComponentName activity = originalInfo.getTargetComponent();
    161         new Handler(workerLooper).postAtFrontOfQueue(new Runnable() {
    162             @Override
    163             public void run() {
    164                 final List<ShortcutInfoCompat> shortcuts = ShortcutFilter.sortAndFilterShortcuts(
    165                         mDeepShortcutsManager.queryForShortcutsContainer(activity, ids, user));
    166                 // We want the lowest rank to be closest to the user's finger.
    167                 if (mIsAboveIcon) {
    168                     Collections.reverse(shortcuts);
    169                 }
    170                 for (int i = 0; i < shortcuts.size(); i++) {
    171                     final ShortcutInfoCompat shortcut = shortcuts.get(i);
    172                     uiHandler.post(new UpdateShortcutChild(
    173                             i, new UnbadgedShortcutInfo(shortcut, mLauncher)));
    174                 }
    175             }
    176         });
    177     }
    178 
    179     /** Updates the child of this container at the given index based on the given shortcut info. */
    180     private class UpdateShortcutChild implements Runnable {
    181         private int mShortcutChildIndex;
    182         private UnbadgedShortcutInfo mShortcutChildInfo;
    183 
    184         public UpdateShortcutChild(int shortcutChildIndex, UnbadgedShortcutInfo shortcutChildInfo) {
    185             mShortcutChildIndex = shortcutChildIndex;
    186             mShortcutChildInfo = shortcutChildInfo;
    187         }
    188 
    189         @Override
    190         public void run() {
    191             getShortcutAt(mShortcutChildIndex)
    192                     .applyShortcutInfo(mShortcutChildInfo, DeepShortcutsContainer.this);
    193         }
    194     }
    195 
    196     private DeepShortcutView getShortcutAt(int index) {
    197         if (!mIsAboveIcon) {
    198             // Opening down, so arrow is the first view.
    199             index++;
    200         }
    201         return (DeepShortcutView) getChildAt(index);
    202     }
    203 
    204     private int getShortcutCount() {
    205         // All children except the arrow are shortcuts.
    206         return getChildCount() - 1;
    207     }
    208 
    209     private void animateOpen() {
    210         setVisibility(View.VISIBLE);
    211         mIsOpen = true;
    212 
    213         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
    214         final int shortcutCount = getShortcutCount();
    215 
    216         final long duration = getResources().getInteger(
    217                 R.integer.config_deepShortcutOpenDuration);
    218         final long arrowScaleDuration = getResources().getInteger(
    219                 R.integer.config_deepShortcutArrowOpenDuration);
    220         final long arrowScaleDelay = duration - arrowScaleDuration;
    221         final long stagger = getResources().getInteger(
    222                 R.integer.config_deepShortcutOpenStagger);
    223         final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
    224 
    225         // Animate shortcuts
    226         DecelerateInterpolator interpolator = new DecelerateInterpolator();
    227         for (int i = 0; i < shortcutCount; i++) {
    228             final DeepShortcutView deepShortcutView = getShortcutAt(i);
    229             deepShortcutView.setVisibility(INVISIBLE);
    230             deepShortcutView.setAlpha(0);
    231 
    232             Animator anim = deepShortcutView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
    233             anim.addListener(new AnimatorListenerAdapter() {
    234                 @Override
    235                 public void onAnimationStart(Animator animation) {
    236                     deepShortcutView.setVisibility(VISIBLE);
    237                 }
    238             });
    239             anim.setDuration(duration);
    240             int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i;
    241             anim.setStartDelay(stagger * animationIndex);
    242             anim.setInterpolator(interpolator);
    243             shortcutAnims.play(anim);
    244 
    245             Animator fadeAnim = new LauncherViewPropertyAnimator(deepShortcutView).alpha(1);
    246             fadeAnim.setInterpolator(fadeInterpolator);
    247             // We want the shortcut to be fully opaque before the arrow starts animating.
    248             fadeAnim.setDuration(arrowScaleDelay);
    249             shortcutAnims.play(fadeAnim);
    250         }
    251         shortcutAnims.addListener(new AnimatorListenerAdapter() {
    252             @Override
    253             public void onAnimationEnd(Animator animation) {
    254                 mOpenCloseAnimator = null;
    255                 Utilities.sendCustomAccessibilityEvent(
    256                         DeepShortcutsContainer.this,
    257                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
    258                         getContext().getString(R.string.action_deep_shortcut));
    259             }
    260         });
    261 
    262         // Animate the arrow
    263         mArrow.setScaleX(0);
    264         mArrow.setScaleY(0);
    265         Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
    266         arrowScale.setStartDelay(arrowScaleDelay);
    267         arrowScale.setDuration(arrowScaleDuration);
    268         shortcutAnims.play(arrowScale);
    269 
    270         mOpenCloseAnimator = shortcutAnims;
    271         shortcutAnims.start();
    272     }
    273 
    274     /**
    275      * Orients this container above or below the given icon, aligning with the left or right.
    276      *
    277      * These are the preferred orientations, in order (RTL prefers right-aligned over left):
    278      * - Above and left-aligned
    279      * - Above and right-aligned
    280      * - Below and left-aligned
    281      * - Below and right-aligned
    282      *
    283      * So we always align left if there is enough horizontal space
    284      * and align above if there is enough vertical space.
    285      */
    286     private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
    287         int width = getMeasuredWidth();
    288         int height = getMeasuredHeight() + arrowHeight;
    289 
    290         DragLayer dragLayer = mLauncher.getDragLayer();
    291         dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
    292         Rect insets = dragLayer.getInsets();
    293 
    294         // Align left (right in RTL) if there is room.
    295         int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
    296         int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
    297         int x = leftAlignedX;
    298         boolean canBeLeftAligned = leftAlignedX + width < dragLayer.getRight() - insets.right;
    299         boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
    300         if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
    301             x = rightAlignedX;
    302         }
    303         mIsLeftAligned = x == leftAlignedX;
    304         if (mIsRtl) {
    305             x -= dragLayer.getWidth() - width;
    306         }
    307 
    308         // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
    309         int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
    310         iconWidth *= icon.getScaleX();
    311         Resources resources = getResources();
    312         int xOffset;
    313         if (isAlignedWithStart()) {
    314             // Aligning with the shortcut icon.
    315             int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
    316             int shortcutPaddingStart = resources.getDimensionPixelSize(
    317                     R.dimen.deep_shortcut_padding_start);
    318             xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
    319         } else {
    320             // Aligning with the drag handle.
    321             int shortcutDragHandleWidth = resources.getDimensionPixelSize(
    322                     R.dimen.deep_shortcut_drag_handle_size);
    323             int shortcutPaddingEnd = resources.getDimensionPixelSize(
    324                     R.dimen.deep_shortcut_padding_end);
    325             xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
    326         }
    327         x += mIsLeftAligned ? xOffset : -xOffset;
    328 
    329         // Open above icon if there is room.
    330         int iconHeight = icon.getIcon().getBounds().height();
    331         int y = mTempRect.top + icon.getPaddingTop() - height;
    332         mIsAboveIcon = y > dragLayer.getTop() + insets.top;
    333         if (!mIsAboveIcon) {
    334             y = mTempRect.top + icon.getPaddingTop() + iconHeight;
    335         }
    336 
    337         // Insets are added later, so subtract them now.
    338         y -= insets.top;
    339 
    340         setX(x);
    341         setY(y);
    342     }
    343 
    344     private boolean isAlignedWithStart() {
    345         return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
    346     }
    347 
    348     /**
    349      * Adds an arrow view pointing at the original icon.
    350      * @param horizontalOffset the horizontal offset of the arrow, so that it
    351      *                              points at the center of the original icon
    352      */
    353     private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
    354         LinearLayout.LayoutParams layoutParams = new LayoutParams(width, height);
    355         if (mIsLeftAligned) {
    356             layoutParams.gravity = Gravity.LEFT;
    357             layoutParams.leftMargin = horizontalOffset;
    358         } else {
    359             layoutParams.gravity = Gravity.RIGHT;
    360             layoutParams.rightMargin = horizontalOffset;
    361         }
    362         if (mIsAboveIcon) {
    363             layoutParams.topMargin = verticalOffset;
    364         } else {
    365             layoutParams.bottomMargin = verticalOffset;
    366         }
    367 
    368         View arrowView = new View(getContext());
    369         ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
    370                 width, height, !mIsAboveIcon));
    371         arrowDrawable.getPaint().setColor(Color.WHITE);
    372         arrowView.setBackground(arrowDrawable);
    373         arrowView.setElevation(getElevation());
    374         addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
    375         return arrowView;
    376     }
    377 
    378     private void deferDrag(BubbleTextView originalIcon) {
    379         mDeferredDragIcon = originalIcon;
    380         mLauncher.getDragController().addDragListener(this);
    381     }
    382 
    383     public BubbleTextView getDeferredDragIcon() {
    384         return mDeferredDragIcon;
    385     }
    386 
    387     /**
    388      * Determines when the deferred drag should be started.
    389      *
    390      * Current behavior:
    391      * - Start the drag if the touch passes a certain distance from the original touch down.
    392      */
    393     public DragOptions.DeferDragCondition createDeferDragCondition(final Runnable onDragStart) {
    394         return new DragOptions.DeferDragCondition() {
    395             @Override
    396             public boolean shouldStartDeferredDrag(double distanceDragged) {
    397                 return distanceDragged > mStartDragThreshold;
    398             }
    399 
    400             @Override
    401             public void onDeferredDragStart() {
    402                 mDeferredDragIcon.setVisibility(INVISIBLE);
    403             }
    404 
    405             @Override
    406             public void onDropBeforeDeferredDrag() {
    407                 mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mDeferredDragIcon);
    408                 if (!mIsAboveIcon) {
    409                     mDeferredDragIcon.setTextVisibility(false);
    410                 }
    411             }
    412 
    413             @Override
    414             public void onDragStart() {
    415                 if (onDragStart != null) {
    416                     onDragStart.run();
    417                 }
    418             }
    419         };
    420     }
    421 
    422     @Override
    423     public boolean onTouch(View v, MotionEvent ev) {
    424         // Touched a shortcut, update where it was touched so we can drag from there on long click.
    425         switch (ev.getAction()) {
    426             case MotionEvent.ACTION_DOWN:
    427             case MotionEvent.ACTION_MOVE:
    428                 mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
    429                 break;
    430         }
    431         return false;
    432     }
    433 
    434     public boolean onLongClick(View v) {
    435         // Return early if this is not initiated from a touch or not the correct view
    436         if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
    437         // Return if global dragging is not enabled
    438         if (!mLauncher.isDraggingEnabled()) return false;
    439 
    440         // Long clicked on a shortcut.
    441         mDeferContainerRemoval = true;
    442         DeepShortcutView sv = (DeepShortcutView) v.getParent();
    443         sv.setWillDrawIcon(false);
    444 
    445         // Move the icon to align with the center-top of the touch point
    446         mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
    447         mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
    448 
    449         DragView dv = mLauncher.getWorkspace().beginDragShared(
    450                 sv.getBubbleText(), this, sv.getFinalInfo(),
    451                 new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
    452         dv.animateShift(-mIconShift.x, -mIconShift.y);
    453 
    454         // TODO: support dragging from within folder without having to close it
    455         mLauncher.closeFolder();
    456         return false;
    457     }
    458 
    459     @Override
    460     public boolean supportsFlingToDelete() {
    461         return true;
    462     }
    463 
    464     @Override
    465     public boolean supportsAppInfoDropTarget() {
    466         return true;
    467     }
    468 
    469     @Override
    470     public boolean supportsDeleteDropTarget() {
    471         return false;
    472     }
    473 
    474     @Override
    475     public float getIntrinsicIconScaleFactor() {
    476         return 1f;
    477     }
    478 
    479     @Override
    480     public void onFlingToDeleteCompleted() {
    481         // Don't care; ignore.
    482     }
    483 
    484     @Override
    485     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
    486             boolean success) {
    487         if (!success) {
    488             d.dragView.remove();
    489             mLauncher.showWorkspace(true);
    490             mLauncher.getDropTargetBar().onDragEnd();
    491         }
    492     }
    493 
    494     @Override
    495     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
    496         // Either the original icon or one of the shortcuts was dragged.
    497         // Hide the container, but don't remove it yet because that interferes with touch events.
    498         animateClose();
    499     }
    500 
    501     @Override
    502     public void onDragEnd() {
    503         if (!mIsOpen) {
    504             if (mOpenCloseAnimator != null) {
    505                 // Close animation is running.
    506                 mDeferContainerRemoval = false;
    507             } else {
    508                 // Close animation is not running.
    509                 if (mDeferContainerRemoval) {
    510                     close();
    511                 }
    512             }
    513         }
    514         mDeferredDragIcon.setVisibility(VISIBLE);
    515     }
    516 
    517     @Override
    518     public void fillInLaunchSourceData(View v, ItemInfo info, Target target, Target targetParent) {
    519         target.itemType = LauncherLogProto.DEEPSHORTCUT;
    520         // TODO: add target.rank
    521         targetParent.containerType = LauncherLogProto.DEEPSHORTCUTS;
    522     }
    523 
    524     public void animateClose() {
    525         if (!mIsOpen) {
    526             return;
    527         }
    528         if (mOpenCloseAnimator != null) {
    529             mOpenCloseAnimator.cancel();
    530         }
    531         mIsOpen = false;
    532 
    533         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
    534         final int shortcutCount = getShortcutCount();
    535         int numOpenShortcuts = 0;
    536         for (int i = 0; i < shortcutCount; i++) {
    537             if (getShortcutAt(i).isOpenOrOpening()) {
    538                 numOpenShortcuts++;
    539             }
    540         }
    541         final long duration = getResources().getInteger(
    542                 R.integer.config_deepShortcutCloseDuration);
    543         final long arrowScaleDuration = getResources().getInteger(
    544                 R.integer.config_deepShortcutArrowOpenDuration);
    545         final long stagger = getResources().getInteger(
    546                 R.integer.config_deepShortcutCloseStagger);
    547         final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
    548 
    549         int firstOpenShortcutIndex = mIsAboveIcon ? shortcutCount - numOpenShortcuts : 0;
    550         for (int i = firstOpenShortcutIndex; i < firstOpenShortcutIndex + numOpenShortcuts; i++) {
    551             final DeepShortcutView view = getShortcutAt(i);
    552             Animator anim;
    553             if (view.willDrawIcon()) {
    554                 anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
    555                 int animationIndex = mIsAboveIcon ? i - firstOpenShortcutIndex
    556                         : numOpenShortcuts - i - 1;
    557                 anim.setStartDelay(stagger * animationIndex);
    558 
    559                 Animator fadeAnim = new LauncherViewPropertyAnimator(view).alpha(0);
    560                 // Don't start fading until the arrow is gone.
    561                 fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
    562                 fadeAnim.setDuration(duration - arrowScaleDuration);
    563                 fadeAnim.setInterpolator(fadeInterpolator);
    564                 shortcutAnims.play(fadeAnim);
    565             } else {
    566                 // The view is being dragged. Animate it such that it collapses with the drag view
    567                 anim = view.collapseToIcon();
    568                 anim.setDuration(DragView.VIEW_ZOOM_DURATION);
    569 
    570                 // Scale and translate the view to follow the drag view.
    571                 Point iconCenter = view.getIconCenter();
    572                 view.setPivotX(iconCenter.x);
    573                 view.setPivotY(iconCenter.y);
    574 
    575                 float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
    576                 LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view)
    577                         .scaleX(scale)
    578                         .scaleY(scale)
    579                         .translationX(mIconShift.x)
    580                         .translationY(mIconShift.y);
    581                 anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
    582                 shortcutAnims.play(anim2);
    583             }
    584             anim.addListener(new AnimatorListenerAdapter() {
    585                 @Override
    586                 public void onAnimationEnd(Animator animation) {
    587                     view.setVisibility(INVISIBLE);
    588                 }
    589             });
    590             shortcutAnims.play(anim);
    591         }
    592         Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow)
    593                 .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
    594         arrowAnim.setStartDelay(0);
    595         shortcutAnims.play(arrowAnim);
    596 
    597         shortcutAnims.addListener(new AnimatorListenerAdapter() {
    598             @Override
    599             public void onAnimationEnd(Animator animation) {
    600                 mOpenCloseAnimator = null;
    601                 if (mDeferContainerRemoval) {
    602                     setVisibility(INVISIBLE);
    603                 } else {
    604                     close();
    605                 }
    606             }
    607         });
    608         mOpenCloseAnimator = shortcutAnims;
    609         shortcutAnims.start();
    610     }
    611 
    612     /**
    613      * Closes the folder without animation.
    614      */
    615     public void close() {
    616         if (mOpenCloseAnimator != null) {
    617             mOpenCloseAnimator.cancel();
    618             mOpenCloseAnimator = null;
    619         }
    620         mIsOpen = false;
    621         mDeferContainerRemoval = false;
    622         boolean isInHotseat = ((ItemInfo) mDeferredDragIcon.getTag()).container
    623                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
    624         mDeferredDragIcon.setTextVisibility(!isInHotseat);
    625         mLauncher.getDragController().removeDragListener(this);
    626         mLauncher.getDragLayer().removeView(this);
    627     }
    628 
    629     public boolean isOpen() {
    630         return mIsOpen;
    631     }
    632 
    633     /**
    634      * Shows the shortcuts container for {@param icon}
    635      * @return the container if shown or null.
    636      */
    637     public static DeepShortcutsContainer showForIcon(BubbleTextView icon) {
    638         Launcher launcher = Launcher.getLauncher(icon.getContext());
    639         if (launcher.getOpenShortcutsContainer() != null) {
    640             // There is already a shortcuts container open, so don't open this one.
    641             icon.clearFocus();
    642             return null;
    643         }
    644         List<String> ids = launcher.getShortcutIdsForItem((ItemInfo) icon.getTag());
    645         if (!ids.isEmpty()) {
    646             // There are shortcuts associated with the app, so defer its drag.
    647             final DeepShortcutsContainer container =
    648                     (DeepShortcutsContainer) launcher.getLayoutInflater().inflate(
    649                             R.layout.deep_shortcuts_container, launcher.getDragLayer(), false);
    650             container.setVisibility(View.INVISIBLE);
    651             launcher.getDragLayer().addView(container);
    652             container.populateAndShow(icon, ids);
    653             return container;
    654         }
    655         return null;
    656     }
    657 
    658     /**
    659      * Extension of {@link ShortcutInfo} which does not badge the icons.
    660      */
    661     static class UnbadgedShortcutInfo extends ShortcutInfo {
    662         public final ShortcutInfoCompat mDetail;
    663 
    664         public UnbadgedShortcutInfo(ShortcutInfoCompat shortcutInfo, Context context) {
    665             super(shortcutInfo, context);
    666             mDetail = shortcutInfo;
    667         }
    668 
    669         @Override
    670         protected Bitmap getBadgedIcon(Bitmap unbadgedBitmap, ShortcutInfoCompat shortcutInfo,
    671                 IconCache cache, Context context) {
    672             return unbadgedBitmap;
    673         }
    674     }
    675 }
    676