2 * Copyright (C) 2016 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.launcher3.popup;
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;
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;
74 import java.util.ArrayList;
75 import java.util.List;
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;
83 * A container for shortcuts to deep links within apps.
85 @TargetApi(Build.VERSION_CODES.N)
86 public class PopupContainerWithArrow extends AbstractFloatingView
87 implements View.OnLongClickListener, View.OnTouchListener, DragSource,
88 DragController.DragListener {
90 private final Point mIconShift = new Point();
91 private final Point mIconLastTouchPos = new Point();
93 protected final Launcher mLauncher;
94 private final int mStartDragThreshold;
95 private LauncherAccessibilityDelegate mAccessibilityDelegate;
96 private final boolean mIsRtl;
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;
105 protected Animator mOpenCloseAnimator;
106 private boolean mDeferContainerRemoval;
108 public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
109 super(context, attrs, defStyleAttr);
110 mLauncher = Launcher.getLauncher(context);
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());
119 public PopupContainerWithArrow(Context context, AttributeSet attrs) {
120 this(context, attrs, 0);
123 public PopupContainerWithArrow(Context context) {
124 this(context, null, 0);
127 public LauncherAccessibilityDelegate getAccessibilityDelegate() {
128 return mAccessibilityDelegate;
132 * Shows the notifications and deep shortcuts associated with {@param icon}.
133 * @return the container if shown or null.
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.
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);
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);
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);
173 measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
174 orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
176 boolean reverseOrder = mIsAboveIcon;
179 itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate);
180 addDummyViews(originalIcon, itemsToPopulate, notificationKeys.length > 1);
182 measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
183 orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset);
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]) {
193 shortcutViews.add(0, (DeepShortcutView) item);
195 shortcutViews.add((DeepShortcutView) item);
199 notificationView = (NotificationItemView) item;
200 IconPalette iconPalette = originalIcon.getIconPalette();
201 notificationView.applyColors(iconPalette);
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());
215 mOriginalIcon = originalIcon;
217 mLauncher.getDragController().addDragListener(this);
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));
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;
241 if (i < numItems - 1) {
242 ((LayoutParams) item.getLayoutParams()).bottomMargin = spacing;
244 item.setAccessibilityDelegate(mAccessibilityDelegate);
247 // TODO: update this, since not all items are shortcuts
248 setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
249 numItems, originalIcon.getContentDescription().toString()));
252 protected PopupItemView getItemViewAt(int index) {
254 // Opening down, so arrow is the first view.
257 return (PopupItemView) getChildAt(index);
260 protected int getItemCount() {
261 // All children except the arrow are items.
262 return getChildCount() - 1;
265 private void animateOpen() {
266 setVisibility(View.VISIBLE);
269 final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
270 final int itemCount = getItemCount();
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);
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);
288 Animator anim = popupItemView.createOpenAnimation(mIsAboveIcon, mIsLeftAligned);
289 anim.addListener(new AnimatorListenerAdapter() {
291 public void onAnimationStart(Animator animation) {
292 popupItemView.setVisibility(VISIBLE);
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);
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);
307 shortcutAnims.addListener(new AnimatorListenerAdapter() {
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));
321 Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
322 arrowScale.setStartDelay(arrowScaleDelay);
323 arrowScale.setDuration(arrowScaleDuration);
324 shortcutAnims.play(arrowScale);
326 mOpenCloseAnimator = shortcutAnims;
327 shortcutAnims.start();
331 * Orients this container above or below the given icon, aligning with the left or right.
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
339 * So we always align left if there is enough horizontal space
340 * and align above if there is enough vertical space.
342 private void orientAboutIcon(BubbleTextView icon, int arrowHeight) {
343 int width = getMeasuredWidth();
344 int height = getMeasuredHeight() + arrowHeight;
346 DragLayer dragLayer = mLauncher.getDragLayer();
347 dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect);
348 Rect insets = dragLayer.getInsets();
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)) {
360 mIsLeftAligned = x == leftAlignedX;
362 x -= dragLayer.getWidth() - width;
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();
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;
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;
384 x += mIsLeftAligned ? xOffset : -xOffset;
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;
391 y = mTempRect.top + icon.getPaddingTop() + iconHeight;
394 // Insets are added later, so subtract them now.
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;
409 if (rightSide + width < dragLayer.getRight()) {
411 mIsLeftAligned = true;
414 mIsLeftAligned = false;
417 if (leftSide > dragLayer.getLeft()) {
419 mIsLeftAligned = false;
422 mIsLeftAligned = true;
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;
433 int gravity = ((FrameLayout.LayoutParams) getLayoutParams()).gravity;
434 if (!Gravity.isHorizontal(gravity)) {
437 if (!Gravity.isVertical(gravity)) {
442 private boolean isAlignedWithStart() {
443 return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
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
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;
457 layoutParams.gravity = Gravity.RIGHT;
458 layoutParams.rightMargin = horizontalOffset;
461 layoutParams.topMargin = verticalOffset;
463 layoutParams.bottomMargin = verticalOffset;
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);
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());
478 addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
483 public View getExtendedTouchView() {
484 return mOriginalIcon;
488 * Determines when the deferred drag should be started.
491 * - Start the drag if the touch passes a certain distance from the original touch down.
493 public DragOptions.PreDragCondition createPreDragCondition() {
494 return new DragOptions.PreDragCondition() {
496 public boolean shouldStartDrag(double distanceDragged) {
497 return distanceDragged > mStartDragThreshold;
501 public void onPreDragStart(DropTarget.DragObject dragObject) {
502 mOriginalIcon.setVisibility(INVISIBLE);
506 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
508 mOriginalIcon.setVisibility(VISIBLE);
509 mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
511 mOriginalIcon.setTextVisibility(false);
519 public boolean onInterceptTouchEvent(MotionEvent ev) {
520 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
521 mInterceptTouchDown.set(ev.getX(), ev.getY());
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();
530 * We need to handle touch events to prevent them from falling through to the workspace below.
532 @SuppressLint("ClickableViewAccessibility")
534 public boolean onTouchEvent(MotionEvent ev) {
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());
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;
558 // Long clicked on a shortcut.
559 mDeferContainerRemoval = true;
560 DeepShortcutView sv = (DeepShortcutView) v.getParent();
561 sv.setWillDrawIcon(false);
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;
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);
572 // TODO: support dragging from within folder without having to close it
573 AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
577 public void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) {
578 final NotificationItemView notificationView = (NotificationItemView) findViewById(R.id.notification_view);
579 if (notificationView == null) {
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,
591 Animator reduceHeight = notificationView.createRemovalAnimation(duration);
592 final View removeMarginView = mIsAboveIcon ? getItemViewAt(getItemCount() - 2)
594 if (removeMarginView != null) {
595 ValueAnimator removeMargin = ValueAnimator.ofFloat(1, 0).setDuration(duration);
596 removeMargin.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
598 public void onAnimationUpdate(ValueAnimator valueAnimator) {
599 ((MarginLayoutParams) removeMarginView.getLayoutParams()).bottomMargin
600 = (int) (spacing * (float) valueAnimator.getAnimatedValue());
603 removeNotification.play(removeMargin);
605 removeNotification.play(reduceHeight);
606 Animator fade = new LauncherViewPropertyAnimator(notificationView).alpha(0)
607 .setDuration(duration);
608 fade.addListener(new AnimatorListenerAdapter() {
610 public void onAnimationEnd(Animator animation) {
611 removeView(notificationView);
612 if (getItemCount() == 0) {
616 View firstItem = getItemViewAt(mIsAboveIcon ? getItemCount() - 1 : 0);
617 mArrow.setBackgroundTintList(firstItem.getBackgroundTintList());
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();
633 notificationView.trimNotifications(badgeInfo.getNotificationKeys());
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.
641 public @Nullable Animator animateTranslationYBy(int translationY, int duration) {
643 return new LauncherViewPropertyAnimator(this)
644 .translationY(getTranslationY() + translationY).setDuration(duration);
650 public boolean supportsAppInfoDropTarget() {
655 public boolean supportsDeleteDropTarget() {
660 public float getIntrinsicIconScaleFactor() {
665 public void onDropCompleted(View target, DropTarget.DragObject d, boolean isFlingToDelete,
669 mLauncher.showWorkspace(true);
670 mLauncher.getDropTargetBar().onDragEnd();
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.
682 public void onDragEnd() {
684 if (mOpenCloseAnimator != null) {
685 // Close animation is running.
686 mDeferContainerRemoval = false;
688 // Close animation is not running.
689 if (mDeferContainerRemoval) {
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;
704 protected void handleClose(boolean animate) {
712 protected void animateClose() {
716 if (mOpenCloseAnimator != null) {
717 mOpenCloseAnimator.cancel();
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()) {
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);
737 int firstOpenItemIndex = mIsAboveIcon ? itemCount - numOpenShortcuts : 0;
738 for (int i = firstOpenItemIndex; i < firstOpenItemIndex + numOpenShortcuts; i++) {
739 final PopupItemView view = getItemViewAt(i);
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);
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);
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);
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);
763 float scale = ((float) mLauncher.getDeviceProfile().iconSizePx) / view.getHeight();
764 LauncherViewPropertyAnimator anim2 = new LauncherViewPropertyAnimator(view)
767 .translationX(mIconShift.x)
768 .translationY(mIconShift.y);
769 anim2.setDuration(DragView.VIEW_ZOOM_DURATION);
770 shortcutAnims.play(anim2);
772 anim.addListener(new AnimatorListenerAdapter() {
774 public void onAnimationEnd(Animator animation) {
775 view.setVisibility(INVISIBLE);
778 shortcutAnims.play(anim);
780 Animator arrowAnim = new LauncherViewPropertyAnimator(mArrow)
781 .scaleX(0).scaleY(0).setDuration(arrowScaleDuration);
782 arrowAnim.setStartDelay(0);
783 shortcutAnims.play(arrowAnim);
785 shortcutAnims.addListener(new AnimatorListenerAdapter() {
787 public void onAnimationEnd(Animator animation) {
788 mOpenCloseAnimator = null;
789 if (mDeferContainerRemoval) {
790 setVisibility(INVISIBLE);
796 mOpenCloseAnimator = shortcutAnims;
797 shortcutAnims.start();
801 * Closes the folder without animation.
803 protected void closeComplete() {
804 if (mOpenCloseAnimator != null) {
805 mOpenCloseAnimator.cancel();
806 mOpenCloseAnimator = null;
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);
818 protected boolean isOfType(int type) {
819 return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0;
823 * Returns a DeepShortcutsContainer which is already open or null
825 public static PopupContainerWithArrow getOpen(Launcher launcher) {
826 return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW);
830 public int getLogContainerType() {
831 return ContainerType.DEEPSHORTCUTS;