| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.launcher3.popup; |
| |
| import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS; |
| import static com.android.launcher3.Utilities.squaredHypot; |
| import static com.android.launcher3.Utilities.squaredTouchSlop; |
| import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE; |
| import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS; |
| import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; |
| |
| import android.animation.AnimatorSet; |
| import android.animation.LayoutTransition; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.graphics.PointF; |
| import android.graphics.Rect; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.util.AttributeSet; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| |
| import androidx.annotation.LayoutRes; |
| |
| import com.android.launcher3.AbstractFloatingView; |
| import com.android.launcher3.BaseDraggingActivity; |
| import com.android.launcher3.BubbleTextView; |
| import com.android.launcher3.DragSource; |
| import com.android.launcher3.DropTarget; |
| import com.android.launcher3.DropTarget.DragObject; |
| import com.android.launcher3.Flags; |
| import com.android.launcher3.Launcher; |
| import com.android.launcher3.R; |
| import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; |
| import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; |
| import com.android.launcher3.dragndrop.DragController; |
| import com.android.launcher3.dragndrop.DragOptions; |
| import com.android.launcher3.dragndrop.DragView; |
| import com.android.launcher3.dragndrop.DraggableView; |
| import com.android.launcher3.model.data.ItemInfo; |
| import com.android.launcher3.model.data.ItemInfoWithIcon; |
| import com.android.launcher3.model.data.WorkspaceItemInfo; |
| import com.android.launcher3.shortcuts.DeepShortcutView; |
| import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; |
| import com.android.launcher3.touch.ItemLongClickListener; |
| import com.android.launcher3.util.PackageUserKey; |
| import com.android.launcher3.util.ShortcutUtil; |
| import com.android.launcher3.views.ActivityContext; |
| import com.android.launcher3.views.BaseDragLayer; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.stream.Collectors; |
| |
| /** |
| * A container for shortcuts to deep links associated with an app. |
| * |
| * @param <T> The activity on with the popup shows |
| */ |
| public class PopupContainerWithArrow<T extends Context & ActivityContext> |
| extends ArrowPopup<T> implements DragSource, DragController.DragListener { |
| |
| private final List<DeepShortcutView> mDeepShortcuts = new ArrayList<>(); |
| private final PointF mInterceptTouchDown = new PointF(); |
| |
| private final int mStartDragThreshold; |
| |
| private static final int SHORTCUT_COLLAPSE_THRESHOLD = 6; |
| |
| private final float mShortcutHeight; |
| |
| private BubbleTextView mOriginalIcon; |
| private int mContainerWidth; |
| |
| private ViewGroup mWidgetContainer; |
| private ViewGroup mDeepShortcutContainer; |
| private ViewGroup mSystemShortcutContainer; |
| |
| protected PopupItemDragHandler mPopupItemDragHandler; |
| protected LauncherAccessibilityDelegate mAccessibilityDelegate; |
| |
| public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) { |
| super(context, attrs, defStyleAttr); |
| mStartDragThreshold = getResources().getDimensionPixelSize( |
| R.dimen.deep_shortcuts_start_drag_threshold); |
| mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width); |
| mShortcutHeight = getResources().getDimension(R.dimen.system_shortcut_header_height); |
| } |
| |
| public PopupContainerWithArrow(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public PopupContainerWithArrow(Context context) { |
| this(context, null, 0); |
| } |
| |
| @Override |
| protected View getAccessibilityInitialFocusView() { |
| if (mSystemShortcutContainer != null) { |
| return mSystemShortcutContainer.getChildAt(0); |
| } |
| return super.getAccessibilityInitialFocusView(); |
| } |
| |
| public LauncherAccessibilityDelegate getAccessibilityDelegate() { |
| return mAccessibilityDelegate; |
| } |
| |
| @Override |
| public boolean onInterceptTouchEvent(MotionEvent ev) { |
| if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| mInterceptTouchDown.set(ev.getX(), ev.getY()); |
| } |
| // Stop sending touch events to deep shortcut views if user moved beyond touch slop. |
| return squaredHypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY()) |
| > squaredTouchSlop(getContext()); |
| } |
| |
| @Override |
| protected boolean isOfType(int type) { |
| return (type & TYPE_ACTION_POPUP) != 0; |
| } |
| |
| public OnClickListener getItemClickListener() { |
| return (view) -> { |
| mActivityContext.getItemOnClickListener().onClick(view); |
| }; |
| } |
| |
| public void setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler) { |
| mPopupItemDragHandler = popupItemDragHandler; |
| } |
| |
| public PopupItemDragHandler getItemDragHandler() { |
| return mPopupItemDragHandler; |
| } |
| |
| @Override |
| public boolean onControllerInterceptTouchEvent(MotionEvent ev) { |
| if (ev.getAction() == MotionEvent.ACTION_DOWN) { |
| BaseDragLayer dl = getPopupContainer(); |
| if (!dl.isEventOverView(this, ev)) { |
| // TODO: add WW log if want to log if tap closed deep shortcut container. |
| 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; |
| } |
| |
| /** |
| * Returns true if we can show the container. |
| * |
| * @deprecated Left here since some dependent projects are using this method |
| */ |
| @Deprecated |
| public static boolean canShow(View icon, ItemInfo item) { |
| return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item); |
| } |
| |
| /** |
| * Shows a popup with shortcuts associated with a Launcher icon |
| * @param icon the app icon to show the popup for |
| * @return the container if shown or null. |
| */ |
| public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) { |
| Launcher launcher = Launcher.getLauncher(icon.getContext()); |
| if (getOpen(launcher) != null) { |
| // There is already an items container open, so don't open this one. |
| icon.clearFocus(); |
| return null; |
| } |
| ItemInfo item = (ItemInfo) icon.getTag(); |
| if (!ShortcutUtil.supportsShortcuts(item)) { |
| return null; |
| } |
| |
| PopupContainerWithArrow<Launcher> container; |
| PopupDataProvider popupDataProvider = launcher.getPopupDataProvider(); |
| int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item); |
| List<SystemShortcut> systemShortcuts = launcher.getSupportedShortcuts() |
| .map(s -> s.getShortcut(launcher, item, icon)) |
| .filter(Objects::nonNull) |
| .collect(Collectors.toList()); |
| container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( |
| R.layout.popup_container, launcher.getDragLayer(), false); |
| container.configureForLauncher(launcher, item); |
| container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts); |
| launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item)); |
| container.requestFocus(); |
| return container; |
| } |
| |
| private void configureForLauncher(Launcher launcher, ItemInfo itemInfo) { |
| addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler( |
| launcher, (PopupContainerWithArrow<Launcher>) this)); |
| if (!Flags.privateSpaceRestrictItemDrag() |
| || !(itemInfo instanceof ItemInfoWithIcon itemInfoWithIcon) |
| || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0) { |
| mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this); |
| } |
| mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher); |
| launcher.getDragController().addDragListener(this); |
| } |
| |
| /** |
| * Populate and show shortcuts for the Launcher U app shortcut design. |
| * Will inflate the container and shortcut View instances for the popup container. |
| * @param originalIcon App icon that the popup is shown for |
| * @param deepShortcutCount Number of DeepShortcutView instances to add to container |
| * @param systemShortcuts List of SystemShortcuts to add to container |
| */ |
| public void populateAndShowRows(final BubbleTextView originalIcon, |
| int deepShortcutCount, List<SystemShortcut> systemShortcuts) { |
| |
| mOriginalIcon = originalIcon; |
| mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width); |
| |
| if (deepShortcutCount > 0) { |
| addAllShortcuts(deepShortcutCount, systemShortcuts); |
| } else if (!systemShortcuts.isEmpty()) { |
| addSystemShortcuts(systemShortcuts, |
| R.layout.system_shortcut_rows_container, |
| R.layout.system_shortcut); |
| } |
| show(); |
| loadAppShortcuts((ItemInfo) originalIcon.getTag()); |
| } |
| |
| /** |
| * Animates and loads shortcuts on background thread for this popup container |
| */ |
| private void loadAppShortcuts(ItemInfo originalItemInfo) { |
| setAccessibilityPaneTitle(getTitleForAccessibility()); |
| mOriginalIcon.setForceHideDot(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. |
| MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( |
| mActivityContext, originalItemInfo, new Handler(Looper.getMainLooper()), |
| this, mDeepShortcuts)); |
| } |
| |
| /** |
| * Adds any Deep Shortcuts, System Shortcuts and the Widget Shortcut to their respective |
| * containers |
| * @param deepShortcutCount number of DeepShortcutView instances |
| * @param systemShortcuts List of SystemShortcuts |
| */ |
| private void addAllShortcuts(int deepShortcutCount, |
| List<SystemShortcut> systemShortcuts) { |
| if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) { |
| // add all system shortcuts including widgets shortcut to same container |
| addSystemShortcuts(systemShortcuts, |
| R.layout.system_shortcut_rows_container, |
| R.layout.system_shortcut); |
| float currentHeight = (mShortcutHeight * systemShortcuts.size()) |
| + mChildContainerMargin; |
| addDeepShortcuts(deepShortcutCount, currentHeight); |
| return; |
| } |
| |
| float currentHeight = mShortcutHeight + mChildContainerMargin; |
| List<SystemShortcut> nonWidgetSystemShortcuts = |
| getNonWidgetSystemShortcuts(systemShortcuts); |
| // If total shortcuts over threshold, collapse system shortcuts to single row |
| addSystemShortcutsIconsOnly(nonWidgetSystemShortcuts); |
| // May need to recalculate row width |
| mContainerWidth = Math.max(mContainerWidth, |
| nonWidgetSystemShortcuts.size() * getResources() |
| .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)); |
| // Add widget shortcut to separate container |
| Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(systemShortcuts); |
| if (widgetShortcutOpt.isPresent()) { |
| mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container_material_u, |
| this); |
| initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get()); |
| currentHeight += mShortcutHeight + mChildContainerMargin; |
| } |
| addDeepShortcuts(deepShortcutCount, currentHeight); |
| } |
| |
| /** |
| * Finds the first instance of the Widgets Shortcut from the SystemShortcut List |
| * @param systemShortcuts List of SystemShortcut instances to search |
| * @return Optional Widgets SystemShortcut |
| */ |
| private static Optional<SystemShortcut.Widgets> getWidgetShortcut( |
| List<SystemShortcut> systemShortcuts) { |
| return systemShortcuts |
| .stream() |
| .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets) |
| .map(SystemShortcut.Widgets.class::cast) |
| .findFirst(); |
| } |
| |
| /** |
| * Returns list of [systemShortcuts] without the Widgets shortcut instance if found |
| * @param systemShortcuts list of SystemShortcuts to filter from |
| * @return systemShortcuts without the Widgets Shortcut |
| */ |
| private static List<SystemShortcut> getNonWidgetSystemShortcuts( |
| List<SystemShortcut> systemShortcuts) { |
| |
| return systemShortcuts |
| .stream() |
| .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets)) |
| .collect(Collectors.toList()); |
| } |
| |
| /** |
| * Inflates the given systemShortcutContainerLayout as a container, and populates with |
| * the systemShortcuts as views using the systemShortcutLayout |
| * @param systemShortcuts List of SystemShortcut to inflate as Views |
| * @param systemShortcutContainerLayout Layout Resource for the Container of shortcut Views |
| * @param systemShortcutLayout Layout Resource for the individual shortcut Views |
| */ |
| private void addSystemShortcuts(List<SystemShortcut> systemShortcuts, |
| @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout) { |
| |
| if (systemShortcuts.size() == 0) { |
| return; |
| } |
| mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this); |
| mWidgetContainer = mSystemShortcutContainer; |
| for (int i = 0; i < systemShortcuts.size(); i++) { |
| initializeSystemShortcut( |
| systemShortcutLayout, |
| mSystemShortcutContainer, |
| systemShortcuts.get(i), |
| i < systemShortcuts.size() - 1); |
| } |
| } |
| |
| private void addSystemShortcutsIconsOnly(List<SystemShortcut> systemShortcuts) { |
| if (systemShortcuts.size() == 0) { |
| return; |
| } |
| |
| mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this); |
| |
| for (int i = 0; i < systemShortcuts.size(); i++) { |
| @LayoutRes int shortcutIconLayout = R.layout.system_shortcut_icon_only; |
| boolean shouldAppendSpacer = true; |
| |
| if (i == 0) { |
| shortcutIconLayout = R.layout.system_shortcut_icon_only_start; |
| } else if (i == systemShortcuts.size() - 1) { |
| shortcutIconLayout = R.layout.system_shortcut_icon_only_end; |
| shouldAppendSpacer = false; |
| } |
| initializeSystemShortcut( |
| shortcutIconLayout, |
| mSystemShortcutContainer, |
| systemShortcuts.get(i), |
| shouldAppendSpacer); |
| } |
| } |
| |
| /** |
| * Inflates and adds [deepShortcutCount] number of DeepShortcutView for the to a new container |
| * @param deepShortcutCount number of DeepShortcutView instances to add |
| * @param currentHeight height of popup before adding deep shortcuts |
| */ |
| private void addDeepShortcuts(int deepShortcutCount, float currentHeight) { |
| mDeepShortcutContainer = inflateAndAdd(R.layout.deep_shortcut_container, this); |
| for (int i = deepShortcutCount; i > 0; i--) { |
| currentHeight += mShortcutHeight; |
| // when there is limited vertical screen space, limit total popup rows to fit |
| if (currentHeight >= mActivityContext.getDeviceProfile().availableHeightPx) break; |
| DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, |
| mDeepShortcutContainer); |
| v.getLayoutParams().width = mContainerWidth; |
| mDeepShortcuts.add(v); |
| } |
| updateHiddenShortcuts(); |
| } |
| |
| protected BubbleTextView getOriginalIcon() { |
| return mOriginalIcon; |
| } |
| |
| protected ViewGroup getSystemShortcutContainer() { |
| return mSystemShortcutContainer; |
| } |
| |
| protected ViewGroup getWidgetContainer() { |
| return mWidgetContainer; |
| } |
| |
| protected void setWidgetContainer(ViewGroup widgetContainer) { |
| mWidgetContainer = widgetContainer; |
| } |
| |
| private String getTitleForAccessibility() { |
| return getContext().getString(R.string.action_deep_shortcut); |
| } |
| |
| @Override |
| protected void getTargetObjectLocation(Rect outPos) { |
| getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos); |
| outPos.top += mOriginalIcon.getPaddingTop(); |
| outPos.left += mOriginalIcon.getPaddingLeft(); |
| outPos.right -= mOriginalIcon.getPaddingRight(); |
| outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null |
| ? mOriginalIcon.getIcon().getBounds().height() |
| : mOriginalIcon.getHeight()); |
| } |
| |
| protected void updateHiddenShortcuts() { |
| int total = mDeepShortcuts.size(); |
| for (int i = 0; i < total; i++) { |
| DeepShortcutView view = mDeepShortcuts.get(i); |
| view.setVisibility(i >= MAX_SHORTCUTS ? GONE : VISIBLE); |
| } |
| } |
| |
| protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) { |
| View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false); |
| view.getLayoutParams().width = mContainerWidth; |
| } |
| |
| /** |
| * Initializes and adds View for given SystemShortcut to a container. |
| * @param resId Resource id to use for SystemShortcut View. |
| * @param container ViewGroup to add the shortcut View to as a parent |
| * @param info The SystemShortcut instance to create a View for. |
| * @param shouldAppendSpacer If True, will add a spacer after the shortcut, when showing the |
| * SystemShortcut as an icon only. Used to space the shortcut icons |
| * evenly. |
| * @return The view inflated for the SystemShortcut |
| */ |
| protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info, |
| boolean shouldAppendSpacer) { |
| View view = inflateAndAdd(resId, container); |
| if (view instanceof DeepShortcutView) { |
| // System shortcut takes entire row with icon and text |
| final DeepShortcutView shortcutView = (DeepShortcutView) view; |
| info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText()); |
| } else if (view instanceof ImageView) { |
| // System shortcut is just an icon |
| info.setIconAndContentDescriptionFor((ImageView) view); |
| if (shouldAppendSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container); |
| view.setTooltipText(view.getContentDescription()); |
| } |
| view.setTag(info); |
| view.setOnClickListener(info); |
| return view; |
| } |
| |
| /** |
| * Determines when the deferred drag should be started. |
| * |
| * Current behavior: |
| * - Start the drag if the touch passes a certain distance from the original touch down. |
| */ |
| public DragOptions.PreDragCondition createPreDragCondition(boolean updateIconUi) { |
| return new DragOptions.PreDragCondition() { |
| |
| @Override |
| public boolean shouldStartDrag(double distanceDragged) { |
| return distanceDragged > mStartDragThreshold; |
| } |
| |
| @Override |
| public void onPreDragStart(DropTarget.DragObject dragObject) { |
| if (!updateIconUi) { |
| return; |
| } |
| if (mIsAboveIcon) { |
| // Hide only the icon, keep the text visible. |
| mOriginalIcon.setIconVisible(false); |
| mOriginalIcon.setVisibility(VISIBLE); |
| } else { |
| // Hide both the icon and text. |
| mOriginalIcon.setVisibility(INVISIBLE); |
| } |
| } |
| |
| @Override |
| public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { |
| if (!updateIconUi) { |
| return; |
| } |
| mOriginalIcon.setIconVisible(true); |
| if (dragStarted) { |
| // Make sure we keep the original icon hidden while it is being dragged. |
| mOriginalIcon.setVisibility(INVISIBLE); |
| } else { |
| // TODO: add WW logging if want to add logging for long press on popup |
| // container. |
| // mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon); |
| if (!mIsAboveIcon) { |
| // Show the icon but keep the text hidden. |
| mOriginalIcon.setVisibility(VISIBLE); |
| mOriginalIcon.setTextVisibility(false); |
| } |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public void onDropCompleted(View target, DragObject d, boolean success) { } |
| |
| @Override |
| public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { |
| // Either the original icon or one of the shortcuts was dragged. |
| // Hide the container, but don't remove it yet because that interferes with touch events. |
| mDeferContainerRemoval = true; |
| animateClose(); |
| } |
| |
| @Override |
| public void onDragEnd() { |
| if (!mIsOpen) { |
| if (mOpenCloseAnimator != null) { |
| // Close animation is running. |
| mDeferContainerRemoval = false; |
| } else { |
| // Close animation is not running. |
| if (mDeferContainerRemoval) { |
| closeComplete(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| protected void onCreateCloseAnimation(AnimatorSet anim) { |
| // Animate original icon's text back in. |
| anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); |
| mOriginalIcon.setForceHideDot(false); |
| } |
| |
| @Override |
| protected void closeComplete() { |
| super.closeComplete(); |
| if (mActivityContext.getDragController() != null) { |
| mActivityContext.getDragController().removeDragListener(this); |
| } |
| PopupContainerWithArrow openPopup = getOpen(mActivityContext); |
| if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) { |
| mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); |
| mOriginalIcon.setForceHideDot(false); |
| } |
| } |
| |
| /** |
| * Returns a PopupContainerWithArrow which is already open or null |
| */ |
| public static <T extends Context & ActivityContext> PopupContainerWithArrow getOpen(T context) { |
| return getOpenView(context, TYPE_ACTION_POPUP); |
| } |
| |
| /** |
| * Dismisses the popup if it is no longer valid |
| */ |
| public static void dismissInvalidPopup(BaseDraggingActivity activity) { |
| PopupContainerWithArrow popup = getOpen(activity); |
| if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow() |
| || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) { |
| popup.animateClose(); |
| } |
| } |
| |
| /** |
| * Handler to control drag-and-drop for popup items |
| */ |
| public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { } |
| |
| /** |
| * Drag and drop handler for popup items in Launcher activity |
| */ |
| public static class LauncherPopupItemDragHandler implements PopupItemDragHandler { |
| |
| protected final Point mIconLastTouchPos = new Point(); |
| private final Launcher mLauncher; |
| private final PopupContainerWithArrow mContainer; |
| |
| LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) { |
| mLauncher = launcher; |
| mContainer = container; |
| } |
| |
| @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) { |
| if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; |
| // Return early if not the correct view |
| if (!(v.getParent() instanceof DeepShortcutView)) 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; |
| |
| DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON); |
| WorkspaceItemInfo itemInfo = sv.getFinalInfo(); |
| itemInfo.container = CONTAINER_SHORTCUTS; |
| DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView, |
| mContainer, itemInfo, |
| 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; |
| } |
| } |
| } |