OSDN Git Service

Merge "Updating the default widget preview generation logic" into ub-launcher3-master
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / popup / PopupContainerWithArrow.java
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.popup;
18
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.AnimatorSet;
22 import android.animation.TimeInterpolator;
23 import android.animation.ValueAnimator;
24 import android.annotation.SuppressLint;
25 import android.annotation.TargetApi;
26 import android.content.Context;
27 import android.content.res.Resources;
28 import android.graphics.Color;
29 import android.graphics.Point;
30 import android.graphics.PointF;
31 import android.graphics.Rect;
32 import android.graphics.drawable.ShapeDrawable;
33 import android.os.Build;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.support.annotation.Nullable;
37 import android.util.AttributeSet;
38 import android.view.Gravity;
39 import android.view.LayoutInflater;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.ViewConfiguration;
43 import android.view.accessibility.AccessibilityEvent;
44 import android.view.animation.DecelerateInterpolator;
45 import android.widget.FrameLayout;
46
47 import com.android.launcher3.AbstractFloatingView;
48 import com.android.launcher3.BubbleTextView;
49 import com.android.launcher3.DragSource;
50 import com.android.launcher3.DropTarget;
51 import com.android.launcher3.ItemInfo;
52 import com.android.launcher3.Launcher;
53 import com.android.launcher3.LauncherAnimUtils;
54 import com.android.launcher3.LauncherModel;
55 import com.android.launcher3.LauncherSettings;
56 import com.android.launcher3.LauncherViewPropertyAnimator;
57 import com.android.launcher3.LogAccelerateInterpolator;
58 import com.android.launcher3.R;
59 import com.android.launcher3.Utilities;
60 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
61 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
62 import com.android.launcher3.badge.BadgeInfo;
63 import com.android.launcher3.dragndrop.DragController;
64 import com.android.launcher3.dragndrop.DragLayer;
65 import com.android.launcher3.dragndrop.DragOptions;
66 import com.android.launcher3.dragndrop.DragView;
67 import com.android.launcher3.graphics.IconPalette;
68 import com.android.launcher3.graphics.TriangleShape;
69 import com.android.launcher3.notification.NotificationItemView;
70 import com.android.launcher3.shortcuts.DeepShortcutView;
71 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
72 import com.android.launcher3.util.PackageUserKey;
73
74 import java.util.ArrayList;
75 import java.util.List;
76 import java.util.Map;
77
78 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
79 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
80 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
81
82 /**
83  * A container for shortcuts to deep links within apps.
84  */
85 @TargetApi(Build.VERSION_CODES.N)
86 public class PopupContainerWithArrow extends AbstractFloatingView
87         implements View.OnLongClickListener, View.OnTouchListener, DragSource,
88         DragController.DragListener {
89
90     private final Point mIconShift = new Point();
91     private final Point mIconLastTouchPos = new Point();
92
93     protected final Launcher mLauncher;
94     private final int mStartDragThreshold;
95     private LauncherAccessibilityDelegate mAccessibilityDelegate;
96     private final boolean mIsRtl;
97
98     protected BubbleTextView mOriginalIcon;
99     private final Rect mTempRect = new Rect();
100     private PointF mInterceptTouchDown = new PointF();
101     private boolean mIsLeftAligned;
102     protected boolean mIsAboveIcon;
103     private View mArrow;
104
105     protected Animator mOpenCloseAnimator;
106     private boolean mDeferContainerRemoval;
107
108     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
109         super(context, attrs, defStyleAttr);
110         mLauncher = Launcher.getLauncher(context);
111
112         mStartDragThreshold = getResources().getDimensionPixelSize(
113                 R.dimen.deep_shortcuts_start_drag_threshold);
114         // TODO: make sure the delegate works for all items, not just shortcuts.
115         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
116         mIsRtl = Utilities.isRtl(getResources());
117     }
118
119     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
120         this(context, attrs, 0);
121     }
122
123     public PopupContainerWithArrow(Context context) {
124         this(context, null, 0);
125     }
126
127     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
128         return mAccessibilityDelegate;
129     }
130
131     /**
132      * Shows the notifications and deep shortcuts associated with {@param icon}.
133      * @return the container if shown or null.
134      */
135     public static PopupContainerWithArrow showForIcon(BubbleTextView icon) {
136         Launcher launcher = Launcher.getLauncher(icon.getContext());
137         if (getOpen(launcher) != null) {
138             // There is already an items container open, so don't open this one.
139             icon.clearFocus();
140             return null;
141         }
142         ItemInfo itemInfo = (ItemInfo) icon.getTag();
143         List<String> shortcutIds = launcher.getPopupDataProvider().getShortcutIdsForItem(itemInfo);
144         String[] notificationKeys = launcher.getPopupDataProvider()
145                 .getNotificationKeysForItem(itemInfo);
146         if (shortcutIds.size() > 0 || notificationKeys.length > 0) {
147             final PopupContainerWithArrow container =
148                     (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
149                             R.layout.popup_container, launcher.getDragLayer(), false);
150             container.setVisibility(View.INVISIBLE);
151             launcher.getDragLayer().addView(container);
152             container.populateAndShow(icon, shortcutIds, notificationKeys);
153             return container;
154         }
155         return null;
156     }
157
158     public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
159             final String[] notificationKeys) {
160         final Resources resources = getResources();
161         final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
162         final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
163         final int arrowHorizontalOffset = resources.getDimensionPixelSize(
164                 R.dimen.deep_shortcuts_arrow_horizontal_offset);
165         final int arrowVerticalOffset = resources.getDimensionPixelSize(
166                 R.dimen.deep_shortcuts_arrow_vertical_offset);
167
168         // Add dummy views first, and populate with real info when ready.
169         PopupPopulator.Item[] itemsToPopulate = PopupPopulator
170                 .getItemsToPopulate(shortcutIds, notificationKeys);
171         addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
172
173         measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
174         orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
175
176         boolean reverseOrder = mIsAboveIcon;
177         if (reverseOrder) {
178             removeAllViews();
179             itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
180             addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
181
182             measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
183             orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
184         }
185
186         List<DeepShortcutView> shortcutViews = new ArrayList<>();
187         NotificationItemView notificationView = null;
188         for (int i = 0; i < getChildCount(); i++) {
189             View item = getChildAt(i);
190             switch (itemsToPopulate[i]) {
191                 case SHORTCUT:
192                     if (reverseOrder) {
193                         shortcutViews.add(0, (DeepShortcutView) item);
194                     } else {
195                         shortcutViews.add((DeepShortcutView) item);
196                     }
197                     break;
198                 case NOTIFICATION:
199                     notificationView = (NotificationItemView) item;
200                     IconPalette iconPalette = originalIcon.getIconPalette();
201                     notificationView.applyColors(iconPalette);
202                     break;
203             }
204         }
205
206         // Add the arrow.
207         mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
208         mArrow.setPivotX(arrowWidth / 2);
209         mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight);
210         PopupItemView firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
211         mArrow.setBackgroundTintList(firstItem.getAttachedArrowColor());
212
213         animateOpen();
214
215         mOriginalIcon = originalIcon;
216
217         mLauncher.getDragController().addDragListener(this);
218
219         // Load the shortcuts on a background thread and update the container as it animates.
220         final Looper workerLooper = LauncherModel.getWorkerLooper();
221         new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
222                 mLauncher, (ItemInfo) originalIcon.getTag(), new Handler(Looper.getMainLooper()),
223                 this, shortcutIds, shortcutViews, notificationKeys, notificationView));
224     }
225
226     private void addDummyViews(BubbleTextView originalIcon,
227             PopupPopulator.Item[] itemsToPopulate, boolean secondaryNotificationViewHasIcons) {
228         final Resources res = getResources();
229         final int spacing = res.getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
230         final LayoutInflater inflater = mLauncher.getLayoutInflater();
231         int numItems = itemsToPopulate.length;
232         for (int i = 0; i < numItems; i++) {
233             final PopupItemView item = (PopupItemView) inflater.inflate(
234                     itemsToPopulate[i].layoutId, this, false);
235             if (itemsToPopulate[i] == PopupPopulator.Item.NOTIFICATION) {
236                 int secondaryHeight = secondaryNotificationViewHasIcons ?
237                         res.getDimensionPixelSize(R.dimen.notification_footer_height) :
238                         res.getDimensionPixelSize(R.dimen.notification_footer_collapsed_height);
239                 item.findViewById(R.id.footer).getLayoutParams().height = secondaryHeight;
240             }
241             if (i < numItems - 1) {
242                 ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
243             }
244             item.setAccessibilityDelegate(mAccessibilityDelegate);
245             addView(item);
246         }
247         // TODO: update this, since not all items are shortcuts
248         setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
249                 numItems, originalIcon.getContentDescription().toString()));
250     }
251
252     protected PopupItemView getItemViewAt(int index) {
253         if (!mIsAboveIcon) {
254             // Opening down, so arrow is the first view.
255             index++;
256         }
257         return (PopupItemView) getChildAt(index);
258     }
259
260     protected int getItemCount() {
261         // All children except the arrow are items.
262         return getChildCount() - 1;
263     }
264
265     private void animateOpen() {
266         setVisibility(View.VISIBLE);
267         mIsOpen = true;
268
269         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
270         final int itemCount = getItemCount();
271
272         final long duration = getResources().getInteger(
273                 R.integer.config_deepShortcutOpenDuration);
274         final long arrowScaleDuration = getResources().getInteger(
275                 R.integer.config_deepShortcutArrowOpenDuration);
276         final long arrowScaleDelay = duration - arrowScaleDuration;
277         final long stagger = getResources().getInteger(
278                 R.integer.config_deepShortcutOpenStagger);
279         final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
280
281         // Animate shortcuts
282         DecelerateInterpolator interpolator = new DecelerateInterpolator();
283         for (int i = 0; i < itemCount; i++) {
284             final PopupItemView popupItemView = getItemViewAt(i);
285             popupItemView.setVisibility(INVISIBLE);
286             popupItemView.setAlpha(0);
287
288             Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
289             anim.addListener(new AnimatorListenerAdapter() {
290                 @Override
291                 public void onAnimationStart(Animator animation) {
292                     popupItemView.setVisibility(VISIBLE);
293                 }
294             });
295             anim.setDuration(duration);
296             int animationIndex = mIsAboveIcon ? itemCount - i - 1 : i;
297             anim.setStartDelay(stagger * animationIndex);
298             anim.setInterpolator(interpolator);
299             shortcutAnims.play(anim);
300
301             Animator fadeAnim = new LauncherViewPropertyAnimator(popupItemView).alpha(1);
302             fadeAnim.setInterpolator(fadeInterpolator);
303             // We want the shortcut to be fully opaque before the arrow starts animating.
304             fadeAnim.setDuration(arrowScaleDelay);
305             shortcutAnims.play(fadeAnim);
306         }
307         shortcutAnims.addListener(new AnimatorListenerAdapter() {
308             @Override
309             public void onAnimationEnd(Animator animation) {
310                 mOpenCloseAnimator = null;
311                 Utilities.sendCustomAccessibilityEvent(
312                         PopupContainerWithArrow.this,
313                         AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
314                         getContext().getString(R.string.action_deep_shortcut));
315             }
316         });
317
318         // Animate the arrow
319         mArrow.setScaleX(0);
320         mArrow.setScaleY(0);
321         Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
322         arrowScale.setStartDelay(arrowScaleDelay);
323         arrowScale.setDuration(arrowScaleDuration);
324         shortcutAnims.play(arrowScale);
325
326         mOpenCloseAnimator = shortcutAnims;
327         shortcutAnims.start();
328     }
329
330     /**
331      * Orients this container above or below the given icon, aligning with the left or right.
332      *
333      * These are the preferred orientations, in order (RTL prefers right-aligned over left):
334      * - Above and left-aligned
335      * - Above and right-aligned
336      * - Below and left-aligned
337      * - Below and right-aligned
338      *
339      * So we always align left if there is enough horizontal space
340      * and align above if there is enough vertical space.
341      */
342     private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
343         int width = getMeasuredWidth();
344         int height = getMeasuredHeight() + arrowHeight;
345
346         DragLayer dragLayer = mLauncher.getDragLayer();
347         dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
348         Rect insets = dragLayer.getInsets();
349
350         // Align left (right in RTL) if there is room.
351         int leftAlignedX = mTempRect.left + icon.getPaddingLeft();
352         int rightAlignedX = mTempRect.right - width - icon.getPaddingRight();
353         int x = leftAlignedX;
354         boolean canBeLeftAligned = leftAlignedX + width + insets.left
355                 < dragLayer.getRight() - insets.right;
356         boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
357         if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
358             x = rightAlignedX;
359         }
360         mIsLeftAligned = x == leftAlignedX;
361         if (mIsRtl) {
362             x -= dragLayer.getWidth() - width;
363         }
364
365         // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
366         int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight();
367         iconWidth *= icon.getScaleX();
368         Resources resources = getResources();
369         int xOffset;
370         if (isAlignedWithStart()) {
371             // Aligning with the shortcut icon.
372             int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
373             int shortcutPaddingStart = resources.getDimensionPixelSize(
374                     R.dimen.deep_shortcut_padding_start);
375             xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
376         } else {
377             // Aligning with the drag handle.
378             int shortcutDragHandleWidth = resources.getDimensionPixelSize(
379                     R.dimen.deep_shortcut_drag_handle_size);
380             int shortcutPaddingEnd = resources.getDimensionPixelSize(
381                     R.dimen.deep_shortcut_padding_end);
382             xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
383         }
384         x += mIsLeftAligned ? xOffset : -xOffset;
385
386         // Open above icon if there is room.
387         int iconHeight = icon.getIcon().getBounds().height();
388         int y = mTempRect.top + icon.getPaddingTop() - height;
389         mIsAboveIcon = y > dragLayer.getTop() + insets.top;
390         if (!mIsAboveIcon) {
391             y = mTempRect.top + icon.getPaddingTop() + iconHeight;
392         }
393
394         // Insets are added later, so subtract them now.
395         if (mIsRtl) {
396             x += insets.right;
397         } else {
398             x -= insets.left;
399         }
400         y -= insets.top;
401
402         if (y < dragLayer.getTop() || y + height > dragLayer.getBottom()) {
403             // The container is opening off the screen, so just center it in the drag layer instead.
404             ((FrameLayout.LayoutParams) getLayoutParams()).gravity = Gravity.CENTER_VERTICAL;
405             // Put the container next to the icon, preferring the right side in ltr (left in rtl).
406             int rightSide = leftAlignedX + iconWidth - insets.left;
407             int leftSide = rightAlignedX - iconWidth - insets.left;
408             if (!mIsRtl) {
409                 if (rightSide + width < dragLayer.getRight()) {
410                     x = rightSide;
411                     mIsLeftAligned = true;
412                 } else {
413                     x = leftSide;
414                     mIsLeftAligned = false;
415                 }
416             } else {
417                 if (leftSide > dragLayer.getLeft()) {
418                     x = leftSide;
419                     mIsLeftAligned = false;
420                 } else {
421                     x = rightSide;
422                     mIsLeftAligned = true;
423                 }
424             }
425             mIsAboveIcon = true;
426         }
427
428         if (x < dragLayer.getLeft() || x + width > dragLayer.getRight()) {
429             // If we are still off screen, center horizontally too.
430             ((FrameLayout.LayoutParams) getLayoutParams()).gravity |= Gravity.CENTER_HORIZONTAL;
431         }
432
433         int gravity = ((FrameLayout.LayoutParams) getLayoutParams()).gravity;
434         if (!Gravity.isHorizontal(gravity)) {
435             setX(x);
436         }
437         if (!Gravity.isVertical(gravity)) {
438             setY(y);
439         }
440     }
441
442     private boolean isAlignedWithStart() {
443         return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
444     }
445
446     /**
447      * Adds an arrow view pointing at the original icon.
448      * @param horizontalOffset the horizontal offset of the arrow, so that it
449      *                              points at the center of the original icon
450      */
451     private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
452         LayoutParams layoutParams = new LayoutParams(width, height);
453         if (mIsLeftAligned) {
454             layoutParams.gravity = Gravity.LEFT;
455             layoutParams.leftMargin = horizontalOffset;
456         } else {
457             layoutParams.gravity = Gravity.RIGHT;
458             layoutParams.rightMargin = horizontalOffset;
459         }
460         if (mIsAboveIcon) {
461             layoutParams.topMargin = verticalOffset;
462         } else {
463             layoutParams.bottomMargin = verticalOffset;
464         }
465
466         View arrowView = new View(getContext());
467         if (Gravity.isVertical(((FrameLayout.LayoutParams) getLayoutParams()).gravity)) {
468             // This is only true if there wasn't room for the container next to the icon,
469             // so we centered it instead. In that case we don't want to show the arrow.
470             arrowView.setVisibility(INVISIBLE);
471         } else {
472             ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
473                     width, height, !mIsAboveIcon));
474             arrowDrawable.getPaint().setColor(Color.WHITE);
475             arrowView.setBackground(arrowDrawable);
476             arrowView.setElevation(getElevation());
477         }
478         addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
479         return arrowView;
480     }
481
482     @Override
483     public View getExtendedTouchView() {
484         return mOriginalIcon;
485     }
486
487     /**
488      * Determines when the deferred drag should be started.
489      *
490      * Current behavior:
491      * - Start the drag if the touch passes a certain distance from the original touch down.
492      */
493     public DragOptions.PreDragCondition createPreDragCondition() {
494         return new DragOptions.PreDragCondition() {
495             @Override
496             public boolean shouldStartDrag(double distanceDragged) {
497                 return distanceDragged > mStartDragThreshold;
498             }
499
500             @Override
501             public void onPreDragStart(DropTarget.DragObject dragObject) {
502                 mOriginalIcon.setVisibility(INVISIBLE);
503             }
504
505             @Override
506             public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
507                 if (!dragStarted) {
508                     mOriginalIcon.setVisibility(VISIBLE);
509                     mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
510                     if (!mIsAboveIcon) {
511                         mOriginalIcon.setTextVisibility(false);
512                     }
513                 }
514             }
515         };
516     }
517
518     @Override
519     public boolean onInterceptTouchEvent(MotionEvent ev) {
520         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
521             mInterceptTouchDown.set(ev.getX(), ev.getY());
522             return false;
523         }
524         // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
525         return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
526                 > ViewConfiguration.get(getContext()).getScaledTouchSlop();
527     }
528
529     /**
530      * We need to handle touch events to prevent them from falling through to the workspace below.
531      */
532     @SuppressLint("ClickableViewAccessibility")
533     @Override
534     public boolean onTouchEvent(MotionEvent ev) {
535         return true;
536     }
537
538     @Override
539     public boolean onTouch(View v, MotionEvent ev) {
540         // Touched a shortcut, update where it was touched so we can drag from there on long click.
541         switch (ev.getAction()) {
542             case MotionEvent.ACTION_DOWN:
543             case MotionEvent.ACTION_MOVE:
544                 mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
545                 break;
546         }
547         return false;
548     }
549
550     public boolean onLongClick(View v) {
551         // Return early if this is not initiated from a touch or not the correct view
552         if (!v.isInTouchMode() || !(v.getParent() instanceof DeepShortcutView)) return false;
553         // Return early if global dragging is not enabled
554         if (!mLauncher.isDraggingEnabled()) return false;
555         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
556         if (mLauncher.getDragController().isDragging()) return false;
557
558         // Long clicked on a shortcut.
559         mDeferContainerRemoval = true;
560         DeepShortcutView sv = (DeepShortcutView) v.getParent();
561         sv.setWillDrawIcon(false);
562
563         // Move the icon to align with the center-top of the touch point
564         mIconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
565         mIconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
566
567         DragView dv = mLauncher.getWorkspace().beginDragShared(
568                 sv.getBubbleText(), this, sv.getFinalInfo(),
569                 new ShortcutDragPreviewProvider(sv.getIconView(), mIconShift), new DragOptions());
570         dv.animateShift(-mIconShift.x, -mIconShift.y);
571
572         // TODO: support dragging from within folder without having to close it
573         AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
574         return false;
575     }
576
577     public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
578         final NotificationItemView notificationView = (NotificationItemView) findViewById(R.id.notification_view);
579         if (notificationView == null) {
580             return;
581         }
582         ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
583         BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
584         if (badgeInfo == null || badgeInfo.getNotificationCount() == 0) {
585             AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
586             final int duration = getResources().getInteger(
587                     R.integer.config_removeNotificationViewDuration);
588             final int spacing = getResources().getDimensionPixelSize(R.dimen.deep_shortcuts_spacing);
589             removeNotification.play(animateTranslationYBy(notificationView.getHeight() + spacing,
590                     duration));
591             Animator reduceHeight = notificationView.createRemovalAnimation(duration);
592             final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
593                     : notificationView;
594             if (removeMarginView != null) {
595                 ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
596                 removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
597                     @Override
598                     public void onAnimationUpdate(ValueAnimator valueAnimator) {
599                         ((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin
600                                 = (int) (spacing * (float) valueAnimator.getAnimatedValue());
601                     }
602                 });
603                 removeNotification.play(removeMargin);
604             }
605             removeNotification.play(reduceHeight);
606             Animator fade = new LauncherViewPropertyAnimator(notificationView).alpha(0)
607                     .setDuration(duration);
608             fade.addListener(new AnimatorListenerAdapter() {
609                 @Override
610                 public void onAnimationEnd(Animator animation) {
611                     removeView(notificationView);
612                     if (getItemCount() == 0) {
613                         close(false);
614                         return;
615                     }
616                     View firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
617                     mArrow.setBackgroundTintList(firstItem.getBackgroundTintList());
618                 }
619             });
620             removeNotification.play(fade);
621             final long arrowScaleDuration = getResources().getInteger(
622                     R.integer.config_deepShortcutArrowOpenDuration);
623             Animator hideArrow = new LauncherViewPropertyAnimator(mArrow)
624                     .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
625             hideArrow.setStartDelay(0);
626             Animator showArrow = new LauncherViewPropertyAnimator(mArrow)
627                     .scaleX(1).scaleY(1).setDuration(arrowScaleDuration);
628             showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
629             removeNotification.playSequentially(hideArrow, showArrow);
630             removeNotification.start();
631             return;
632         }
633         notificationView.trimNotifications(badgeInfo.getNotificationKeys());
634     }
635
636     /**
637      * Animates the translationY of this container if it is open above the icon.
638      * If it is below the icon, the container already shifts up when the height
639      * of a child (e.g. NotificationView) changes, so the translation isn't necessary.
640      */
641     public @Nullable Animator animateTranslationYBy(int translationY, int duration) {
642         if (mIsAboveIcon) {
643             return new LauncherViewPropertyAnimator(this)
644                     .translationY(getTranslationY() + translationY).setDuration(duration);
645         }
646         return null;
647     }
648
649     @Override
650     public boolean supportsAppInfoDropTarget() {
651         return true;
652     }
653
654     @Override
655     public boolean supportsDeleteDropTarget() {
656         return false;
657     }
658
659     @Override
660     public float getIntrinsicIconScaleFactor() {
661         return 1f;
662     }
663
664     @Override
665     public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
666             boolean success) {
667         if (!success) {
668             d.dragView.remove();
669             mLauncher.showWorkspace(true);
670             mLauncher.getDropTargetBar().onDragEnd();
671         }
672     }
673
674     @Override
675     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
676         // Either the original icon or one of the shortcuts was dragged.
677         // Hide the container, but don't remove it yet because that interferes with touch events.
678         animateClose();
679     }
680
681     @Override
682     public void onDragEnd() {
683         if (!mIsOpen) {
684             if (mOpenCloseAnimator != null) {
685                 // Close animation is running.
686                 mDeferContainerRemoval = false;
687             } else {
688                 // Close animation is not running.
689                 if (mDeferContainerRemoval) {
690                     closeComplete();
691                 }
692             }
693         }
694     }
695
696     @Override
697     public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
698         target.itemType = ItemType.DEEPSHORTCUT;
699         target.rank = info.rank;
700         targetParent.containerType = ContainerType.DEEPSHORTCUTS;
701     }
702
703     @Override
704     protected void handleClose(boolean animate) {
705         if (animate) {
706             animateClose();
707         } else {
708             closeComplete();
709         }
710     }
711
712     protected void animateClose() {
713         if (!mIsOpen) {
714             return;
715         }
716         if (mOpenCloseAnimator != null) {
717             mOpenCloseAnimator.cancel();
718         }
719         mIsOpen = false;
720
721         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
722         final int itemCount = getItemCount();
723         int numOpenShortcuts = 0;
724         for (int i = 0; i < itemCount; i++) {
725             if (getItemViewAt(i).isOpenOrOpening()) {
726                 numOpenShortcuts++;
727             }
728         }
729         final long duration = getResources().getInteger(
730                 R.integer.config_deepShortcutCloseDuration);
731         final long arrowScaleDuration = getResources().getInteger(
732                 R.integer.config_deepShortcutArrowOpenDuration);
733         final long stagger = getResources().getInteger(
734                 R.integer.config_deepShortcutCloseStagger);
735         final TimeInterpolator fadeInterpolator = new LogAccelerateInterpolator(100, 0);
736
737         int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0;
738         for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
739             final PopupItemView view = getItemViewAt(i);
740             Animator anim;
741             if (view.willDrawIcon()) {
742                 anim = view.createCloseAnimation(mIsAboveIcon, mIsLeftAligned, duration);
743                 int animationIndex = mIsAboveIcon ? i - firstOpenItemIndex
744                         : numOpenShortcuts - i - 1;
745                 anim.setStartDelay(stagger * animationIndex);
746
747                 Animator fadeAnim = new LauncherViewPropertyAnimator(view).alpha(0);
748                 // Don't start fading until the arrow is gone.
749                 fadeAnim.setStartDelay(stagger * animationIndex + arrowScaleDuration);
750                 fadeAnim.setDuration(duration - arrowScaleDuration);
751                 fadeAnim.setInterpolator(fadeInterpolator);
752                 shortcutAnims.play(fadeAnim);
753             } else {
754                 // The view is being dragged. Animate it such that it collapses with the drag view
755                 anim = view.collapseToIcon();
756                 anim.setDuration(DragView.VIEW_ZOOM_DURATION);
757
758                 // Scale and translate the view to follow the drag view.
759                 Point iconCenter = view.getIconCenter();
760                 view.setPivotX(iconCenter.x);
761                 view.setPivotY(iconCenter.y);
762
763                 float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
764                 LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view)
765                         .scaleX(scale)
766                         .scaleY(scale)
767                         .translationX(mIconShift.x)
768                         .translationY(mIconShift.y);
769                 anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
770                 shortcutAnims.play(anim2);
771             }
772             anim.addListener(new AnimatorListenerAdapter() {
773                 @Override
774                 public void onAnimationEnd(Animator animation) {
775                     view.setVisibility(INVISIBLE);
776                 }
777             });
778             shortcutAnims.play(anim);
779         }
780         Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow)
781                 .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
782         arrowAnim.setStartDelay(0);
783         shortcutAnims.play(arrowAnim);
784
785         shortcutAnims.addListener(new AnimatorListenerAdapter() {
786             @Override
787             public void onAnimationEnd(Animator animation) {
788                 mOpenCloseAnimator = null;
789                 if (mDeferContainerRemoval) {
790                     setVisibility(INVISIBLE);
791                 } else {
792                     closeComplete();
793                 }
794             }
795         });
796         mOpenCloseAnimator = shortcutAnims;
797         shortcutAnims.start();
798     }
799
800     /**
801      * Closes the folder without animation.
802      */
803     protected void closeComplete() {
804         if (mOpenCloseAnimator != null) {
805             mOpenCloseAnimator.cancel();
806             mOpenCloseAnimator = null;
807         }
808         mIsOpen = false;
809         mDeferContainerRemoval = false;
810         boolean isInHotseat = ((ItemInfo) mOriginalIcon.getTag()).container
811                 == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
812         mOriginalIcon.setTextVisibility(!isInHotseat);
813         mLauncher.getDragController().removeDragListener(this);
814         mLauncher.getDragLayer().removeView(this);
815     }
816
817     @Override
818     protected boolean isOfType(int type) {
819         return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0;
820     }
821
822     /**
823      * Returns a DeepShortcutsContainer which is already open or null
824      */
825     public static PopupContainerWithArrow getOpen(Launcher launcher) {
826         return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW);
827     }
828
829     @Override
830     public int getLogContainerType() {
831         return ContainerType.DEEPSHORTCUTS;
832     }
833 }