summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/popup/PopupContainerWithArrow.java
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2017-11-09 15:24:06 -0800
committerSunny Goyal <sunnygoyal@google.com>2017-11-15 16:43:20 -0800
commit00ac9202417cb5023b9990b2f6f33d321378ff26 (patch)
tree28cd25a215dbe9bb568aa1a11394a5ae38e3a8a6 /src/com/android/launcher3/popup/PopupContainerWithArrow.java
parentea529083bd45bae8edcb86d0be056ff90921d0c1 (diff)
downloadpackages_apps_Trebuchet-00ac9202417cb5023b9990b2f6f33d321378ff26.tar.gz
packages_apps_Trebuchet-00ac9202417cb5023b9990b2f6f33d321378ff26.tar.bz2
packages_apps_Trebuchet-00ac9202417cb5023b9990b2f6f33d321378ff26.zip
Simplifying app icon popup
> Using a single linearLayout instead of multiple nested views > Using clipToOutline for rounded corners instead of using canvas.saveLayer > Removing nested view elevations and overdraw > Using LayoutTransition for animating layout changes, instead of manually creating animators Change-Id: I8e57092f52ca5a032a2756594fdd39788acc5a0d
Diffstat (limited to 'src/com/android/launcher3/popup/PopupContainerWithArrow.java')
-rw-r--r--src/com/android/launcher3/popup/PopupContainerWithArrow.java925
1 files changed, 664 insertions, 261 deletions
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 4435afb8f..3dc58a1f2 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -16,26 +16,39 @@
package com.android.launcher3.popup;
-import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
-import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Outline;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
import android.view.View;
-
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.ImageView;
+
+import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -45,42 +58,103 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.R;
-import com.android.launcher3.anim.PropertyResetListener;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
import com.android.launcher3.badge.BadgeInfo;
import com.android.launcher3.dragndrop.DragController;
+import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.DragOptions;
+import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.graphics.IconPalette;
+import com.android.launcher3.graphics.TriangleShape;
+import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.notification.NotificationInfo;
import com.android.launcher3.notification.NotificationItemView;
import com.android.launcher3.notification.NotificationKeyData;
-import com.android.launcher3.popup.PopupPopulator.Item;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.shortcuts.ShortcutsItemView;
+import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.Themes;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
+import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.Target;
+
/**
* A container for shortcuts to deep links and notifications associated with an app.
*/
@TargetApi(Build.VERSION_CODES.N)
-public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> implements DragSource,
- DragController.DragListener {
+public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource,
+ DragController.DragListener, View.OnLongClickListener,
+ View.OnTouchListener {
+
+ private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
+ private final PointF mInterceptTouchDown = new PointF();
+ private final Rect mTempRect = new Rect();
+ private final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
+ private final LayoutInflater mInflater;
+ private final float mOutlineRadius;
+ private final Launcher mLauncher;
+ private final LauncherAccessibilityDelegate mAccessibilityDelegate;
+ private final boolean mIsRtl;
+
+ private final int mArrayOffset;
+ private final View mArrow;
+ private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
- private AnimatorSet mReduceHeightAnimatorSet;
+
+ private ViewGroup mSystemShortcutContainer;
+
+ private boolean mIsLeftAligned;
+ protected boolean mIsAboveIcon;
private int mNumNotifications;
+ private int mGravity;
+
+ protected Animator mOpenCloseAnimator;
+ protected boolean mDeferContainerRemoval;
+ private final Rect mStartRect = new Rect();
+ private final Rect mEndRect = new Rect();
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
+ mInflater = LayoutInflater.from(context);
+ mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius);
+ mLauncher = Launcher.getLauncher(context);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
+ mIsRtl = Utilities.isRtl(getResources());
+
+ setClipToOutline(true);
+ setOutlineProvider(new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius);
+ }
+ });
+
+ // Initialize arrow view
+ final Resources resources = getResources();
+ final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width);
+ final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height);
+ mArrow = new View(context);
+ mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight));
+ mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -91,6 +165,75 @@ public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> imp
this(context, null, 0);
}
+ public LauncherAccessibilityDelegate getAccessibilityDelegate() {
+ return mAccessibilityDelegate;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mInterceptTouchDown.set(ev.getX(), ev.getY());
+ }
+ if (mNotificationItemView != null
+ && mNotificationItemView.onInterceptTouchEvent(ev)) {
+ return true;
+ }
+ // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
+ return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
+ > ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ if (mNotificationItemView != null) {
+ return mNotificationItemView.onTouchEvent(ev);
+ }
+ return super.onTouchEvent(ev);
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ACTION_POPUP) != 0;
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ mLauncher.getUserEventDispatcher().logActionCommand(
+ command, mOriginalIcon, ContainerType.DEEPSHORTCUTS);
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ DragLayer dl = mLauncher.getDragLayer();
+ if (!dl.isEventOverView(this, ev)) {
+ mLauncher.getUserEventDispatcher().logActionTapOutside(
+ LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS));
+ close(true);
+
+ // We let touches on the original icon go through so that users can launch
+ // the app with one tap if they don't find a shortcut they want.
+ return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ if (animate) {
+ animateClose();
+ } else {
+ closeComplete();
+ }
+ }
+
+ public <T extends View> T inflateAndAdd(int resId) {
+ View view = mInflater.inflate(resId, this, false);
+ addView(view);
+ return (T) view;
+ }
+
/**
* Shows the notifications and deep shortcuts associated with {@param icon}.
* @return the container if shown or null.
@@ -124,188 +267,420 @@ public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> imp
private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
- PopupPopulator.Item[] itemsToPopulate = PopupPopulator
- .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts);
- populateAndShow(originalIcon, itemsToPopulate);
- ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
- List<DeepShortcutView> shortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getDeepShortcutViews(mIsAboveIcon);
- List<View> systemShortcutViews = mShortcutsItemView == null
- ? Collections.EMPTY_LIST
- : mShortcutsItemView.getSystemShortcutViews(mIsAboveIcon);
- if (mNotificationItemView != null) {
+ setVisibility(View.INVISIBLE);
+ mLauncher.getDragLayer().addView(this);
+
+ mOriginalIcon = originalIcon;
+
+ // Add views
+ if (mNumNotifications > 0) {
+ // Add notification entries
+ View.inflate(getContext(), R.layout.notification_content, this);
+ mNotificationItemView = new NotificationItemView(this);
+ if (mNumNotifications == 1) {
+ mNotificationItemView.removeFooter();
+ }
updateNotificationHeader();
}
+ int viewsToFlip = getChildCount();
+ mSystemShortcutContainer = this;
+
+ if (!shortcutIds.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
+
+ for (int i = shortcutIds.size(); i > 0; i--) {
+ mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut));
+ }
+ updateHiddenShortcuts();
+
+ if (!systemShortcuts.isEmpty()) {
+ mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons);
+ for (SystemShortcut shortcut : systemShortcuts) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, shortcut);
+ }
+ }
+ } else if (!systemShortcuts.isEmpty()) {
+ if (mNotificationItemView != null) {
+ mNotificationItemView.addGutter();
+ }
- int numShortcuts = shortcutViews.size() + systemShortcutViews.size();
- int numNotifications = notificationKeys.size();
- if (numNotifications == 0) {
+ for (SystemShortcut shortcut : systemShortcuts) {
+ initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut);
+ }
+ }
+ orientAboutIcon();
+
+ boolean reverseOrder = mIsAboveIcon;
+ if (reverseOrder) {
+ int count = getChildCount();
+ ArrayList<View> allViews = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ if (i == viewsToFlip) {
+ Collections.reverse(allViews);
+ }
+ allViews.add(getChildAt(i));
+ }
+ Collections.reverse(allViews);
+ removeAllViews();
+ for (int i = 0; i < count; i++) {
+ addView(allViews.get(i));
+ }
+ if (mNotificationItemView != null) {
+ mNotificationItemView.inverseGutterMargin();
+ }
+
+ orientAboutIcon();
+ }
+ updateDividers();
+
+ // Add the arrow.
+ final int arrowHorizontalOffset = getResources().getDimensionPixelSize(isAlignedWithStart()
+ ? R.dimen.popup_arrow_horizontal_offset_start
+ : R.dimen.popup_arrow_horizontal_offset_end);
+ mLauncher.getDragLayer().addView(mArrow);
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsLeftAligned) {
+ mArrow.setX(getX() + arrowHorizontalOffset);
+ } else {
+ mArrow.setX(getX() + getMeasuredWidth() - arrowHorizontalOffset);
+ }
+
+ if (Gravity.isVertical(mGravity)) {
+ // This is only true if there wasn't room for the container next to the icon,
+ // so we centered it instead. In that case we don't want to show the arrow.
+ mArrow.setVisibility(INVISIBLE);
+ } else {
+ ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+ arrowLp.width, arrowLp.height, !mIsAboveIcon));
+ Paint arrowPaint = arrowDrawable.getPaint();
+ arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary));
+ // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+ int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius);
+ arrowPaint.setPathEffect(new CornerPathEffect(radius));
+ mArrow.setBackground(arrowDrawable);
+ mArrow.setElevation(getElevation());
+ }
+
+ mArrow.setPivotX(arrowLp.width / 2);
+ mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height);
+
+ animateOpen();
+
+ ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag();
+ int numShortcuts = mShortcuts.size() + systemShortcuts.size();
+ if (mNumNotifications == 0) {
setContentDescription(getContext().getString(R.string.shortcuts_menu_description,
numShortcuts, originalIcon.getContentDescription().toString()));
} else {
setContentDescription(getContext().getString(
R.string.shortcuts_menu_with_notifications_description, numShortcuts,
- numNotifications, originalIcon.getContentDescription().toString()));
+ mNumNotifications, originalIcon.getContentDescription().toString()));
}
mLauncher.getDragController().addDragListener(this);
mOriginalIcon.forceHideBadge(true);
+ // All views are added. Animate layout from now on.
+ setLayoutTransition(new LayoutTransition());
+
// Load the shortcuts on a background thread and update the container as it animates.
final Looper workerLooper = LauncherModel.getWorkerLooper();
new Handler(workerLooper).postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()),
- this, shortcutIds, shortcutViews, notificationKeys, mNotificationItemView,
- systemShortcuts, systemShortcutViews));
+ this, shortcutIds, mShortcuts, notificationKeys));
}
- @Override
- protected void addDummyViews(Item[] itemTypesToPopulate) {
- mNotificationItemView = null;
- super.addDummyViews(itemTypesToPopulate);
- if (mNumNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
+ protected boolean isAlignedWithStart() {
+ return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl;
+ }
+
+ /**
+ * Orients this container above or below the given icon, aligning with the left or right.
+ *
+ * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+ * - Above and left-aligned
+ * - Above and right-aligned
+ * - Below and left-aligned
+ * - Below and right-aligned
+ *
+ * So we always align left if there is enough horizontal space
+ * and align above if there is enough vertical space.
+ */
+ protected void orientAboutIcon() {
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ int width = getMeasuredWidth();
+ int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset
+ + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
+ int height = getMeasuredHeight() + extraVerticalSpace;
+
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect);
+ Rect insets = dragLayer.getInsets();
+
+ // Align left (right in RTL) if there is room.
+ int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft();
+ int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight();
+ int x = leftAlignedX;
+ boolean canBeLeftAligned = leftAlignedX + width + insets.left
+ < dragLayer.getRight() - insets.right;
+ boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left;
+ if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) {
+ x = rightAlignedX;
+ }
+ mIsLeftAligned = x == leftAlignedX;
+ if (mIsRtl) {
+ x -= dragLayer.getWidth() - width;
+ }
+
+ // Offset x so that the arrow and shortcut icons are center-aligned with the original icon.
+ int iconWidth = mOriginalIcon.getWidth()
+ - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight();
+ iconWidth *= mOriginalIcon.getScaleX();
+ Resources resources = getResources();
+ int xOffset;
+ if (isAlignedWithStart()) {
+ // Aligning with the shortcut icon.
+ int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size);
+ int shortcutPaddingStart = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_start);
+ xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart;
+ } else {
+ // Aligning with the drag handle.
+ int shortcutDragHandleWidth = resources.getDimensionPixelSize(
+ R.dimen.deep_shortcut_drag_handle_size);
+ int shortcutPaddingEnd = resources.getDimensionPixelSize(
+ R.dimen.popup_padding_end);
+ xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd;
+ }
+ x += mIsLeftAligned ? xOffset : -xOffset;
+
+ // Open above icon if there is room.
+ int iconHeight = getIconHeightForPopupPlacement();
+ int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height;
+ mIsAboveIcon = y > dragLayer.getTop() + insets.top;
+ if (!mIsAboveIcon) {
+ y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight + extraVerticalSpace;
+ }
+
+ // Insets are added later, so subtract them now.
+ if (mIsRtl) {
+ x += insets.right;
+ } else {
+ x -= insets.left;
+ }
+ y -= insets.top;
+
+ mGravity = 0;
+ if (y + height > dragLayer.getBottom() - insets.bottom) {
+ // The container is opening off the screen, so just center it in the drag layer instead.
+ mGravity = Gravity.CENTER_VERTICAL;
+ // Put the container next to the icon, preferring the right side in ltr (left in rtl).
+ int rightSide = leftAlignedX + iconWidth - insets.left;
+ int leftSide = rightAlignedX - iconWidth - insets.left;
+ if (!mIsRtl) {
+ if (rightSide + width < dragLayer.getRight()) {
+ x = rightSide;
+ mIsLeftAligned = true;
+ } else {
+ x = leftSide;
+ mIsLeftAligned = false;
+ }
+ } else {
+ if (leftSide > dragLayer.getLeft()) {
+ x = leftSide;
+ mIsLeftAligned = false;
+ } else {
+ x = rightSide;
+ mIsLeftAligned = true;
+ }
+ }
+ mIsAboveIcon = true;
+ }
+
+ setX(x);
+ if (Gravity.isVertical(mGravity)) {
+ return;
+ }
+
+ DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams();
+ DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams();
+ if (mIsAboveIcon) {
+ arrowLp.gravity = lp.gravity = Gravity.BOTTOM;
+ lp.bottomMargin =
+ mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top;
+ arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom;
+ } else {
+ arrowLp.gravity = lp.gravity = Gravity.TOP;
+ lp.topMargin = y + insets.top;
+ arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset;
}
}
@Override
- protected void onViewInflated(View view, Item itemType,
- boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) {
- if (itemType == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) view;
- boolean notificationFooterHasIcons = mNumNotifications > 1;
- int footerHeight = getResources().getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- view.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ // enforce contained is within screen
+ DragLayer dragLayer = mLauncher.getDragLayer();
+ if (getTranslationX() + l < 0 ||
+ getTranslationX() + r > dragLayer.getWidth()) {
+ // If we are still off screen, center horizontally too.
+ mGravity |= Gravity.CENTER_HORIZONTAL;
+ }
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
-
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemType == PopupPopulator.Item.SHORTCUT) {
- view.setAccessibilityDelegate(mAccessibilityDelegate);
- }
-
- if (itemType != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON && itemType.isShortcut
- && mNumNotifications > 0) {
- int prevHeight = view.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- view.getLayoutParams().height = getResources().getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (view instanceof DeepShortcutView) {
- float iconScale = (float) view.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) view).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) view).getIconView().setScaleY(iconScale);
- }
+ if (Gravity.isHorizontal(mGravity)) {
+ setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2);
+ mArrow.setVisibility(INVISIBLE);
+ }
+ if (Gravity.isVertical(mGravity)) {
+ setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2);
}
}
- private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) {
+ protected void animateOpen() {
+ setVisibility(View.VISIBLE);
+ mIsOpen = true;
+
+ final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet();
final Resources res = getResources();
- final LayoutInflater inflater = mLauncher.getLayoutInflater();
-
- int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- int numItems = itemTypesToPopulate.length;
- for (int i = 0; i < numItems; i++) {
- PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i];
- PopupPopulator.Item prevItemTypeToPopulate =
- i > 0 ? itemTypesToPopulate[i - 1] : null;
- PopupPopulator.Item nextItemTypeToPopulate =
- i < numItems - 1 ? itemTypesToPopulate[i + 1] : null;
- final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false);
-
- boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut;
- boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null
- && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut;
-
- if (itemTypeToPopulate == PopupPopulator.Item.NOTIFICATION) {
- mNotificationItemView = (NotificationItemView) item;
- boolean notificationFooterHasIcons = numNotifications > 1;
- int footerHeight = res.getDimensionPixelSize(
- notificationFooterHasIcons ? R.dimen.notification_footer_height
- : R.dimen.notification_empty_footer_height);
- item.findViewById(R.id.footer).getLayoutParams().height = footerHeight;
- if (notificationFooterHasIcons) {
- mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE);
- }
+ final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration);
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal.
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, false);
+ revealAnim.setDuration(revealDuration);
+ revealAnim.setInterpolator(revealInterpolator);
+
+ Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1);
+ fadeIn.setDuration(revealDuration);
+ fadeIn.setInterpolator(revealInterpolator);
+ openAnim.play(fadeIn);
+
+ // Animate the arrow.
+ mArrow.setScaleX(0);
+ mArrow.setScaleY(0);
+ Animator arrowScale = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1)
+ .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration));
+
+ openAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ Utilities.sendCustomAccessibilityEvent(
+ PopupContainerWithArrow.this,
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
+ getContext().getString(R.string.action_deep_shortcut));
+ }
+ });
- int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS;
- if (shouldUnroundTopCorners) {
- roundedCorners &= ~ROUNDED_TOP_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE);
- }
- if (shouldUnroundBottomCorners) {
- roundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
- mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE);
- }
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary);
- mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners);
+ mOpenCloseAnimator = openAnim;
+ openAnim.playSequentially(revealAnim, arrowScale);
+ openAnim.start();
+ }
- mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate);
- } else if (itemTypeToPopulate == PopupPopulator.Item.SHORTCUT) {
- item.setAccessibilityDelegate(mAccessibilityDelegate);
- }
+ public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
+ mNotificationItemView.applyNotificationInfos(notificationInfos);
+ }
- if (itemTypeToPopulate.isShortcut) {
- if (mShortcutsItemView == null) {
- mShortcutsItemView = (ShortcutsItemView) inflater.inflate(
- R.layout.shortcuts_item, this, false);
- addView(mShortcutsItemView);
- if (shouldUnroundTopCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS;
- }
- }
- if (itemTypeToPopulate != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON
- && numNotifications > 0) {
- int prevHeight = item.getLayoutParams().height;
- // Condense shortcuts height when there are notifications.
- item.getLayoutParams().height = res.getDimensionPixelSize(
- R.dimen.bg_popup_item_condensed_height);
- if (item instanceof DeepShortcutView) {
- float iconScale = (float) item.getLayoutParams().height / prevHeight;
- ((DeepShortcutView) item).getIconView().setScaleX(iconScale);
- ((DeepShortcutView) item).getIconView().setScaleY(iconScale);
- }
- }
- mShortcutsItemView.addShortcutView(item, itemTypeToPopulate);
- if (shouldUnroundBottomCorners) {
- shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS;
+ private void updateHiddenShortcuts() {
+ int allowedCount = mNotificationItemView != null
+ ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
+ int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height);
+ int itemHeight = mNotificationItemView != null ?
+ getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height)
+ : originalHeight;
+ float iconScale = ((float) itemHeight) / originalHeight;
+
+ int total = mShortcuts.size();
+ for (int i = 0; i < total; i++) {
+ DeepShortcutView view = mShortcuts.get(i);
+ view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
+ view.getLayoutParams().height = itemHeight;
+ view.getIconView().setScaleX(iconScale);
+ view.getIconView().setScaleY(iconScale);
+ }
+ }
+
+ private void updateDividers() {
+ int count = getChildCount();
+ DeepShortcutView lastView = null;
+ for (int i = 0; i < count; i++) {
+ View view = getChildAt(i);
+ if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) {
+ if (lastView != null) {
+ lastView.setDividerVisibility(VISIBLE);
}
- } else {
- addView(item);
+ lastView = (DeepShortcutView) view;
+ lastView.setDividerVisibility(INVISIBLE);
}
}
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners);
- if (numNotifications > 0) {
- mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS);
- }
}
@Override
protected void onWidgetsBound() {
- if (mShortcutsItemView != null) {
- mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon);
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = new SystemShortcut.Widgets();
+ View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (onClickListener != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != this) {
+ View view = mInflater.inflate(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, false);
+ mSystemShortcutContainer.addView(view);
+ initializeSystemShortcut(view, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need to
+ // reopen the container to ensure measurements etc. all work out. While this could
+ // be quite janky, in practice the user would typically see a small flicker as the
+ // animation restarts partway through, and this is a very rare edge case anyway.
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (onClickListener == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ ((PopupContainerWithArrow) getParent()).close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
}
}
- @Override
+ private void initializeSystemShortcut(View view, SystemShortcut info) {
+ if (view instanceof DeepShortcutView) {
+ // Expanded system shortcut, with both icon and text shown on white background.
+ final DeepShortcutView shortcutView = (DeepShortcutView) view;
+ shortcutView.getIconView().setBackgroundResource(info.iconResId);
+ shortcutView.getBubbleText().setText(info.labelResId);
+ } else if (view instanceof ImageView) {
+ // Only the system shortcut icon shows on a gray background header.
+ final ImageView shortcutIcon = (ImageView) view;
+ shortcutIcon.setImageResource(info.iconResId);
+ shortcutIcon.setContentDescription(getContext().getText(info.labelResId));
+ }
+ view.setTag(info);
+ view.setOnClickListener(info.getOnClickListener(mLauncher,
+ (ItemInfo) mOriginalIcon.getTag()));
+ }
+
protected int getIconHeightForPopupPlacement() {
return mOriginalIcon.getIcon() != null
? mOriginalIcon.getIcon().getBounds().height()
@@ -383,108 +758,15 @@ public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> imp
ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
BadgeInfo badgeInfo = updatedBadges.get(PackageUserKey.fromItemInfo(originalInfo));
if (badgeInfo == null || badgeInfo.getNotificationKeys().size() == 0) {
- // There are no more notifications, so create an animation to remove
- // the notifications view and expand the shortcuts view (if possible).
- AnimatorSet removeNotification = LauncherAnimUtils.createAnimatorSet();
- int hiddenShortcutsHeight = 0;
- if (mShortcutsItemView != null) {
- hiddenShortcutsHeight = mShortcutsItemView.getHiddenShortcutsHeight();
- int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary);
- // With notifications gone, all corners of shortcuts item should be rounded.
- mShortcutsItemView.setBackgroundWithCorners(backgroundColor,
- ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS);
- removeNotification.play(mShortcutsItemView.showAllShortcuts(mIsAboveIcon));
- }
- final int duration = getResources().getInteger(
- R.integer.config_removeNotificationViewDuration);
- removeNotification.play(adjustItemHeights(mNotificationItemView.getHeightMinusFooter(),
- hiddenShortcutsHeight, duration));
- Animator fade = ObjectAnimator.ofFloat(mNotificationItemView, ALPHA, 0)
- .setDuration(duration);
- fade.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- removeView(mNotificationItemView);
- mNotificationItemView = null;
- if (getItemCount() == 0) {
- close(false);
- }
- }
- });
- removeNotification.play(fade);
- final long arrowScaleDuration = getResources().getInteger(
- R.integer.config_popupArrowOpenDuration);
- Animator hideArrow = createArrowScaleAnim(0).setDuration(arrowScaleDuration);
- hideArrow.setStartDelay(0);
- Animator showArrow = createArrowScaleAnim(1).setDuration(arrowScaleDuration);
- showArrow.setStartDelay((long) (duration - arrowScaleDuration * 1.5));
- removeNotification.playSequentially(hideArrow, showArrow);
- removeNotification.start();
- return;
- }
- mNotificationItemView.trimNotifications(NotificationKeyData.extractKeysOnly(
- badgeInfo.getNotificationKeys()));
- }
-
- public Animator reduceNotificationViewHeight(int heightToRemove, int duration) {
- return adjustItemHeights(heightToRemove, 0, duration);
- }
-
- /**
- * Animates the height of the notification item and the translationY of other items accordingly.
- */
- public Animator adjustItemHeights(int notificationHeightToRemove, int shortcutHeightToAdd,
- int duration) {
- if (mReduceHeightAnimatorSet != null) {
- mReduceHeightAnimatorSet.cancel();
- }
- final int translateYBy = mIsAboveIcon ? notificationHeightToRemove - shortcutHeightToAdd
- : -notificationHeightToRemove;
- mReduceHeightAnimatorSet = LauncherAnimUtils.createAnimatorSet();
- boolean removingNotification =
- notificationHeightToRemove == mNotificationItemView.getHeightMinusFooter();
- boolean shouldRemoveNotificationHeightFromTop = mIsAboveIcon && removingNotification;
- mReduceHeightAnimatorSet.play(mNotificationItemView.animateHeightRemoval(
- notificationHeightToRemove, shouldRemoveNotificationHeightFromTop));
- PropertyResetListener<View, Float> resetTranslationYListener
- = new PropertyResetListener<>(TRANSLATION_Y, 0f);
- boolean itemIsAfterShortcuts = false;
- for (int i = 0; i < getItemCount(); i++) {
- final PopupItemView itemView = getItemViewAt(i);
- if (itemIsAfterShortcuts) {
- // Every item after the shortcuts item needs to adjust for the new height.
- itemView.setTranslationY(itemView.getTranslationY() - shortcutHeightToAdd);
- }
- if (itemView == mNotificationItemView && (!mIsAboveIcon || removingNotification)) {
- // The notification view is already in the right place.
- continue;
- }
- ValueAnimator translateItem = ObjectAnimator.ofFloat(itemView, TRANSLATION_Y,
- itemView.getTranslationY() + translateYBy).setDuration(duration);
- translateItem.addListener(resetTranslationYListener);
- mReduceHeightAnimatorSet.play(translateItem);
- if (itemView == mShortcutsItemView) {
- itemIsAfterShortcuts = true;
- }
- }
- if (mIsAboveIcon) {
- // We also need to adjust the arrow position to account for the new shortcuts height.
- mArrow.setTranslationY(mArrow.getTranslationY() - shortcutHeightToAdd);
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(badgeInfo.getNotificationKeys()));
}
- mReduceHeightAnimatorSet.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mIsAboveIcon) {
- // All the items, including the notification item, translated down, but the
- // container itself did not. This means the items would jump back to their
- // original translation unless we update the container's translationY here.
- setTranslationY(getTranslationY() + translateYBy);
- mArrow.setTranslationY(0);
- }
- mReduceHeightAnimatorSet = null;
- }
- });
- return mReduceHeightAnimatorSet;
}
@Override
@@ -515,25 +797,146 @@ public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> imp
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
- target.itemType = ItemType.DEEPSHORTCUT;
+ if (info == NOTIFICATION_ITEM_INFO) {
+ target.itemType = ItemType.NOTIFICATION;
+ } else {
+ target.itemType = ItemType.DEEPSHORTCUT;
+ target.rank = info.rank;
+ }
targetParent.containerType = ContainerType.DEEPSHORTCUTS;
}
- @Override
- protected void prepareCloseAnimator(AnimatorSet closeAnim) {
+ protected void animateClose() {
+ if (!mIsOpen) {
+ return;
+ }
+ mEndRect.setEmpty();
+ if (mOpenCloseAnimator != null) {
+ Outline outline = new Outline();
+ getOutlineProvider().getOutline(this, outline);
+ outline.getRect(mEndRect);
+ mOpenCloseAnimator.cancel();
+ }
+ mIsOpen = false;
+
+ final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet();
+ // Hide the arrow
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0));
+ closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0));
+
// Animate original icon's text back in.
closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
-
mOriginalIcon.forceHideBadge(false);
- super.prepareCloseAnimator(closeAnim);
+
+ final Resources res = getResources();
+ final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator();
+
+ // Rectangular reveal (reversed).
+ final ValueAnimator revealAnim = createOpenCloseOutlineProvider()
+ .createRevealAnimator(this, true);
+ revealAnim.setInterpolator(revealInterpolator);
+ closeAnim.play(revealAnim);
+
+ Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0);
+ fadeOut.setInterpolator(revealInterpolator);
+ closeAnim.play(fadeOut);
+ closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration));
+
+ closeAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mOpenCloseAnimator = null;
+ if (mDeferContainerRemoval) {
+ setVisibility(INVISIBLE);
+ } else {
+ closeComplete();
+ }
+ }
+ });
+ mOpenCloseAnimator = closeAnim;
+ closeAnim.start();
}
- @Override
- protected void closeComplete() {
+ private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
+ int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ?
+ R.dimen.popup_arrow_horizontal_center_start:
+ R.dimen.popup_arrow_horizontal_center_end);
+ if (!mIsLeftAligned) {
+ arrowCenterX = getMeasuredWidth() - arrowCenterX;
+ }
+ int arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0;
+
+ mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY);
+ if (mEndRect.isEmpty()) {
+ mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ return new RoundedRectRevealOutlineProvider
+ (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect);
+ }
+
+ /**
+ * Closes the popup without animation.
+ */
+ private void closeComplete() {
mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
mOriginalIcon.forceHideBadge(false);
mLauncher.getDragController().removeDragListener(this);
- super.closeComplete();
+ if (mOpenCloseAnimator != null) {
+ mOpenCloseAnimator.cancel();
+ mOpenCloseAnimator = null;
+ }
+ mIsOpen = false;
+ mDeferContainerRemoval = false;
+ mLauncher.getDragLayer().removeView(this);
+ mLauncher.getDragLayer().removeView(mArrow);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ // Return early if not the correct view
+ if (!(v.getParent() instanceof DeepShortcutView)) return false;
+ // Return early if global dragging is not enabled
+ if (!mLauncher.isDraggingEnabled()) return false;
+ // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
+ if (mLauncher.getDragController().isDragging()) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ this, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+
+ /**
+ * Returns a PopupContainerWithArrow which is already open or null
+ */
+ public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ return getOpenView(launcher, TYPE_ACTION_POPUP);
}
}