• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.popup;
18 
19 import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace;
20 
21 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
22 import static com.android.launcher3.Utilities.squaredHypot;
23 import static com.android.launcher3.Utilities.squaredTouchSlop;
24 import static com.android.launcher3.allapps.AlphabeticalAppsList.PRIVATE_SPACE_PACKAGE;
25 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE;
26 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
27 import static com.android.launcher3.shortcuts.DeepShortcutTextView.GOOGLE_SANS_FLEX_LABEL_LARGE;
28 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
29 import static com.android.wm.shell.Flags.enableGsf;
30 
31 import android.animation.AnimatorSet;
32 import android.animation.LayoutTransition;
33 import android.content.Context;
34 import android.graphics.Point;
35 import android.graphics.PointF;
36 import android.graphics.Rect;
37 import android.graphics.Typeface;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.util.AttributeSet;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewGroup;
44 import android.widget.ImageView;
45 
46 import androidx.annotation.LayoutRes;
47 
48 import com.android.launcher3.AbstractFloatingView;
49 import com.android.launcher3.BubbleTextView;
50 import com.android.launcher3.DragSource;
51 import com.android.launcher3.DropTarget;
52 import com.android.launcher3.DropTarget.DragObject;
53 import com.android.launcher3.Flags;
54 import com.android.launcher3.Launcher;
55 import com.android.launcher3.R;
56 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
57 import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate;
58 import com.android.launcher3.dragndrop.DragController;
59 import com.android.launcher3.dragndrop.DragOptions;
60 import com.android.launcher3.dragndrop.DragView;
61 import com.android.launcher3.dragndrop.DraggableView;
62 import com.android.launcher3.model.data.ItemInfo;
63 import com.android.launcher3.model.data.ItemInfoWithIcon;
64 import com.android.launcher3.model.data.WorkspaceItemInfo;
65 import com.android.launcher3.shortcuts.DeepShortcutView;
66 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
67 import com.android.launcher3.touch.ItemLongClickListener;
68 import com.android.launcher3.util.PackageUserKey;
69 import com.android.launcher3.util.ShortcutUtil;
70 import com.android.launcher3.views.ActivityContext;
71 import com.android.launcher3.views.BaseDragLayer;
72 
73 import java.util.ArrayList;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.Objects;
77 import java.util.Optional;
78 import java.util.stream.Collectors;
79 
80 /**
81  * A container for shortcuts to deep links associated with an app.
82  *
83  * @param <T> The activity on with the popup shows
84  */
85 public class PopupContainerWithArrow<T extends Context & ActivityContext>
86         extends ArrowPopup<T> implements DragSource, DragController.DragListener {
87 
88     private final List<DeepShortcutView> mDeepShortcuts = new ArrayList<>();
89     private final PointF mInterceptTouchDown = new PointF();
90 
91     private final int mStartDragThreshold;
92 
93     private static final int SHORTCUT_COLLAPSE_THRESHOLD = 6;
94 
95     private final float mShortcutHeight;
96 
97     private BubbleTextView mOriginalIcon;
98     private int mContainerWidth;
99 
100     private ViewGroup mWidgetContainer;
101     private ViewGroup mDeepShortcutContainer;
102     private ViewGroup mSystemShortcutContainer;
103 
104     protected PopupItemDragHandler mPopupItemDragHandler;
105     protected LauncherAccessibilityDelegate mAccessibilityDelegate;
106 
PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr)107     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
108         super(context, attrs, defStyleAttr);
109         mStartDragThreshold = getResources().getDimensionPixelSize(
110                 R.dimen.deep_shortcuts_start_drag_threshold);
111         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
112         mShortcutHeight = getResources().getDimension(R.dimen.system_shortcut_header_height);
113     }
114 
PopupContainerWithArrow(Context context, AttributeSet attrs)115     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
116         this(context, attrs, 0);
117     }
118 
PopupContainerWithArrow(Context context)119     public PopupContainerWithArrow(Context context) {
120         this(context, null, 0);
121     }
122 
123     @Override
getAccessibilityInitialFocusView()124     protected View getAccessibilityInitialFocusView() {
125         if (mSystemShortcutContainer != null) {
126             return mSystemShortcutContainer.getChildAt(0);
127         }
128         return super.getAccessibilityInitialFocusView();
129     }
130 
getAccessibilityDelegate()131     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
132         return mAccessibilityDelegate;
133     }
134 
135     @Override
onInterceptTouchEvent(MotionEvent ev)136     public boolean onInterceptTouchEvent(MotionEvent ev) {
137         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
138             mInterceptTouchDown.set(ev.getX(), ev.getY());
139         }
140         // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
141         return squaredHypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
142                 > squaredTouchSlop(getContext());
143     }
144 
145     @Override
isOfType(int type)146     protected boolean isOfType(int type) {
147         return (type & TYPE_ACTION_POPUP) != 0;
148     }
149 
getItemClickListener()150     public OnClickListener getItemClickListener() {
151         return (view) -> {
152             mActivityContext.getItemOnClickListener().onClick(view);
153         };
154     }
155 
setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler)156     public void setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler) {
157         mPopupItemDragHandler = popupItemDragHandler;
158     }
159 
getItemDragHandler()160     public PopupItemDragHandler getItemDragHandler() {
161         return mPopupItemDragHandler;
162     }
163 
164     @Override
onControllerInterceptTouchEvent(MotionEvent ev)165     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
166         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
167             BaseDragLayer dl = getPopupContainer();
168             if (!dl.isEventOverView(this, ev)) {
169                 // TODO: add WW log if want to log if tap closed deep shortcut container.
170                 close(true);
171 
172                 // We let touches on the original icon go through so that users can launch
173                 // the app with one tap if they don't find a shortcut they want.
174                 return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
175             }
176         }
177         return false;
178     }
179 
180     /**
181      * Returns true if we can show the container.
182      *
183      * @deprecated Left here since some dependent projects are using this method
184      */
185     @Deprecated
canShow(View icon, ItemInfo item)186     public static boolean canShow(View icon, ItemInfo item) {
187         return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item);
188     }
189 
190     /**
191      * Shows a popup with shortcuts associated with a Launcher icon
192      * @param icon the app icon to show the popup for
193      * @return the container if shown or null.
194      */
showForIcon(BubbleTextView icon)195     public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) {
196         Launcher launcher = Launcher.getLauncher(icon.getContext());
197         if (getOpen(launcher) != null) {
198             // There is already an items container open, so don't open this one.
199             icon.clearFocus();
200             return null;
201         }
202         ItemInfo item = (ItemInfo) icon.getTag();
203         if (!ShortcutUtil.supportsShortcuts(item)) {
204             return null;
205         }
206 
207         PopupContainerWithArrow<Launcher> container;
208         PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
209         int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item);
210         List<SystemShortcut> systemShortcuts = launcher.getSupportedShortcuts()
211                 .map(s -> s.getShortcut(launcher, item, icon))
212                 .filter(Objects::nonNull)
213                 .collect(Collectors.toList());
214         container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
215                 R.layout.popup_container, launcher.getDragLayer(), false);
216         container.configureForLauncher(launcher, item);
217         boolean shouldHideSystemShortcuts = enableMovingContentIntoPrivateSpace()
218                 && Objects.equals(item.getTargetPackage(), PRIVATE_SPACE_PACKAGE);
219         container.populateAndShowRows(icon, deepShortcutCount,
220                 shouldHideSystemShortcuts ? Collections.emptyList() : systemShortcuts);
221         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
222         container.requestFocus();
223         return container;
224     }
225 
configureForLauncher(Launcher launcher, ItemInfo itemInfo)226     private void configureForLauncher(Launcher launcher, ItemInfo itemInfo) {
227         addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler(
228                 launcher, (PopupContainerWithArrow<Launcher>) this));
229         if (!Flags.privateSpaceRestrictItemDrag()
230                 || !(itemInfo instanceof ItemInfoWithIcon itemInfoWithIcon)
231                 || (itemInfoWithIcon.runtimeStatusFlags & FLAG_NOT_PINNABLE) == 0) {
232             mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
233         }
234         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
235         launcher.getDragController().addDragListener(this);
236     }
237 
238     /**
239      * Populate and show shortcuts for the Launcher U app shortcut design.
240      * Will inflate the container and shortcut View instances for the popup container.
241      * @param originalIcon App icon that the popup is shown for
242      * @param deepShortcutCount Number of DeepShortcutView instances to add to container
243      * @param systemShortcuts List of SystemShortcuts to add to container
244      */
populateAndShowRows(final BubbleTextView originalIcon, int deepShortcutCount, List<SystemShortcut> systemShortcuts)245     public void populateAndShowRows(final BubbleTextView originalIcon,
246             int deepShortcutCount, List<SystemShortcut> systemShortcuts) {
247         populateAndShowRows(originalIcon, (ItemInfo) originalIcon.getTag(), deepShortcutCount,
248                 systemShortcuts);
249     }
250 
251     /**
252      * Populate and show shortcuts for the Launcher U app shortcut design.
253      * Will inflate the container and shortcut View instances for the popup container.
254      * @param originalIcon App icon that the popup is shown for
255      * @param itemInfo The info that is used to load app shortcuts
256      * @param deepShortcutCount Number of DeepShortcutView instances to add to container
257      * @param systemShortcuts List of SystemShortcuts to add to container
258      */
populateAndShowRows(final BubbleTextView originalIcon, ItemInfo itemInfo, int deepShortcutCount, List<SystemShortcut> systemShortcuts)259     public void populateAndShowRows(final BubbleTextView originalIcon, ItemInfo itemInfo,
260             int deepShortcutCount, List<SystemShortcut> systemShortcuts) {
261 
262         mOriginalIcon = originalIcon;
263         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
264 
265         if (deepShortcutCount > 0) {
266             addAllShortcuts(deepShortcutCount, systemShortcuts);
267         } else if (!systemShortcuts.isEmpty()) {
268             addSystemShortcuts(systemShortcuts,
269                     R.layout.system_shortcut_rows_container,
270                     R.layout.system_shortcut);
271         }
272         show();
273         loadAppShortcuts(itemInfo);
274     }
275 
276     /**
277      * Animates and loads shortcuts on background thread for this popup container
278      */
loadAppShortcuts(ItemInfo originalItemInfo)279     private void loadAppShortcuts(ItemInfo originalItemInfo) {
280         setAccessibilityPaneTitle(getTitleForAccessibility());
281         mOriginalIcon.setForceHideDot(true);
282         // All views are added. Animate layout from now on.
283         setLayoutTransition(new LayoutTransition());
284         // Load the shortcuts on a background thread and update the container as it animates.
285         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
286                 mActivityContext, originalItemInfo, new Handler(Looper.getMainLooper()),
287                 this, mDeepShortcuts));
288     }
289 
290     /**
291      * Adds any Deep Shortcuts, System Shortcuts and the Widget Shortcut to their respective
292      * containers
293      * @param deepShortcutCount number of DeepShortcutView instances
294      * @param systemShortcuts List of SystemShortcuts
295      */
addAllShortcuts(int deepShortcutCount, List<SystemShortcut> systemShortcuts)296     private void addAllShortcuts(int deepShortcutCount,
297             List<SystemShortcut> systemShortcuts) {
298         if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) {
299             // add all system shortcuts including widgets shortcut to same container
300             addSystemShortcuts(systemShortcuts,
301                     R.layout.system_shortcut_rows_container,
302                     R.layout.system_shortcut);
303             float currentHeight = (mShortcutHeight * systemShortcuts.size())
304                     + mChildContainerMargin;
305             addDeepShortcuts(deepShortcutCount, currentHeight);
306             return;
307         }
308 
309         float currentHeight = mShortcutHeight + mChildContainerMargin;
310         List<SystemShortcut> nonWidgetSystemShortcuts =
311                 getNonWidgetSystemShortcuts(systemShortcuts);
312         // If total shortcuts over threshold, collapse system shortcuts to single row
313         addSystemShortcutsIconsOnly(nonWidgetSystemShortcuts);
314         // May need to recalculate row width
315         mContainerWidth = Math.max(mContainerWidth,
316                 nonWidgetSystemShortcuts.size() * getResources()
317                         .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size));
318         // Add widget shortcut to separate container
319         Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(systemShortcuts);
320         if (widgetShortcutOpt.isPresent()) {
321             mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container_material_u,
322                     this);
323             initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
324             currentHeight += mShortcutHeight + mChildContainerMargin;
325         }
326         addDeepShortcuts(deepShortcutCount, currentHeight);
327     }
328 
329     /**
330      * Finds the first instance of the Widgets Shortcut from the SystemShortcut List
331      * @param systemShortcuts List of SystemShortcut instances to search
332      * @return Optional Widgets SystemShortcut
333      */
getWidgetShortcut( List<SystemShortcut> systemShortcuts)334     private static Optional<SystemShortcut.Widgets> getWidgetShortcut(
335             List<SystemShortcut> systemShortcuts) {
336         return systemShortcuts
337                 .stream()
338                 .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
339                 .map(SystemShortcut.Widgets.class::cast)
340                 .findFirst();
341     }
342 
343     /**
344      * Returns list of [systemShortcuts] without the Widgets shortcut instance if found
345      * @param systemShortcuts list of SystemShortcuts to filter from
346      * @return systemShortcuts without the Widgets Shortcut
347      */
getNonWidgetSystemShortcuts( List<SystemShortcut> systemShortcuts)348     private static List<SystemShortcut> getNonWidgetSystemShortcuts(
349             List<SystemShortcut> systemShortcuts) {
350 
351         return systemShortcuts
352                 .stream()
353                 .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
354                 .collect(Collectors.toList());
355     }
356 
357     /**
358      * Inflates the given systemShortcutContainerLayout as a container, and populates with
359      * the systemShortcuts as views using the systemShortcutLayout
360      * @param systemShortcuts List of SystemShortcut to inflate as Views
361      * @param systemShortcutContainerLayout Layout Resource for the Container of shortcut Views
362      * @param systemShortcutLayout Layout Resource for the individual shortcut Views
363      */
addSystemShortcuts(List<SystemShortcut> systemShortcuts, @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout)364     private void addSystemShortcuts(List<SystemShortcut> systemShortcuts,
365             @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout) {
366 
367         if (systemShortcuts.size() == 0) {
368             return;
369         }
370         mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this);
371         mWidgetContainer = mSystemShortcutContainer;
372         for (int i = 0; i < systemShortcuts.size(); i++) {
373             initializeSystemShortcut(
374                     systemShortcutLayout,
375                     mSystemShortcutContainer,
376                     systemShortcuts.get(i),
377                     i < systemShortcuts.size() - 1);
378         }
379     }
380 
addSystemShortcutsIconsOnly(List<SystemShortcut> systemShortcuts)381     private void addSystemShortcutsIconsOnly(List<SystemShortcut> systemShortcuts) {
382         if (systemShortcuts.size() == 0) {
383             return;
384         }
385 
386         mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons_container, this);
387 
388         for (int i = 0; i < systemShortcuts.size(); i++) {
389             @LayoutRes int shortcutIconLayout = R.layout.system_shortcut_icon_only;
390             boolean shouldAppendSpacer = true;
391 
392             if (i == 0) {
393                 shortcutIconLayout = R.layout.system_shortcut_icon_only_start;
394             } else if (i == systemShortcuts.size() - 1) {
395                 shortcutIconLayout = R.layout.system_shortcut_icon_only_end;
396                 shouldAppendSpacer = false;
397             }
398             initializeSystemShortcut(
399                     shortcutIconLayout,
400                     mSystemShortcutContainer,
401                     systemShortcuts.get(i),
402                     shouldAppendSpacer);
403         }
404     }
405 
406     /**
407      * Inflates and adds [deepShortcutCount] number of DeepShortcutView for the  to a new container
408      * @param deepShortcutCount number of DeepShortcutView instances to add
409      * @param currentHeight height of popup before adding deep shortcuts
410      */
addDeepShortcuts(int deepShortcutCount, float currentHeight)411     private void addDeepShortcuts(int deepShortcutCount, float currentHeight) {
412         mDeepShortcutContainer = inflateAndAdd(R.layout.deep_shortcut_container, this);
413         for (int i = deepShortcutCount; i > 0; i--) {
414             currentHeight += mShortcutHeight;
415             // when there is limited vertical screen space, limit total popup rows to fit
416             if (currentHeight >= mActivityContext.getDeviceProfile().availableHeightPx) break;
417             DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut,
418                     mDeepShortcutContainer);
419             v.getLayoutParams().width = mContainerWidth;
420             mDeepShortcuts.add(v);
421         }
422         updateHiddenShortcuts();
423     }
424 
getOriginalIcon()425     protected BubbleTextView getOriginalIcon() {
426         return mOriginalIcon;
427     }
428 
getSystemShortcutContainer()429     protected ViewGroup getSystemShortcutContainer() {
430         return mSystemShortcutContainer;
431     }
432 
getWidgetContainer()433     protected ViewGroup getWidgetContainer() {
434         return mWidgetContainer;
435     }
436 
setWidgetContainer(ViewGroup widgetContainer)437     protected void setWidgetContainer(ViewGroup widgetContainer) {
438         mWidgetContainer = widgetContainer;
439     }
440 
getTitleForAccessibility()441     private String getTitleForAccessibility() {
442         return getContext().getString(R.string.action_deep_shortcut);
443     }
444 
445     @Override
getTargetObjectLocation(Rect outPos)446     protected void getTargetObjectLocation(Rect outPos) {
447         getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
448         outPos.top += mOriginalIcon.getPaddingTop();
449         outPos.left += mOriginalIcon.getPaddingLeft();
450         outPos.right -= mOriginalIcon.getPaddingRight();
451         outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null
452                 ? mOriginalIcon.getIcon().getBounds().height()
453                 : mOriginalIcon.getHeight());
454     }
455 
updateHiddenShortcuts()456     protected void updateHiddenShortcuts() {
457         int total = mDeepShortcuts.size();
458         for (int i = 0; i < total; i++) {
459             DeepShortcutView view = mDeepShortcuts.get(i);
460             view.setVisibility(i >= MAX_SHORTCUTS ? GONE : VISIBLE);
461         }
462     }
463 
initializeWidgetShortcut(ViewGroup container, SystemShortcut info)464     protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) {
465         View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false);
466         view.getLayoutParams().width = mContainerWidth;
467     }
468 
469     /**
470      * Initializes and adds View for given SystemShortcut to a container.
471      * @param resId Resource id to use for SystemShortcut View.
472      * @param container ViewGroup to add the shortcut View to as a parent
473      * @param info The SystemShortcut instance to create a View for.
474      * @param shouldAppendSpacer If True, will add a spacer after the shortcut, when showing the
475      *                        SystemShortcut as an icon only. Used to space the shortcut icons
476      *                        evenly.
477      * @return The view inflated for the SystemShortcut
478      */
initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info, boolean shouldAppendSpacer)479     protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info,
480             boolean shouldAppendSpacer) {
481         View view = inflateAndAdd(resId, container);
482         if (view instanceof DeepShortcutView) {
483             // System shortcut takes entire row with icon and text
484             final DeepShortcutView shortcutView = (DeepShortcutView) view;
485             if (enableGsf()) {
486                 shortcutView.getBubbleText().setTypeface(
487                         Typeface.create(GOOGLE_SANS_FLEX_LABEL_LARGE, Typeface.NORMAL));
488             }
489             info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
490         } else if (view instanceof ImageView) {
491             // System shortcut is just an icon
492             info.setIconAndContentDescriptionFor((ImageView) view);
493             if (shouldAppendSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container);
494             view.setTooltipText(view.getContentDescription());
495         }
496         view.setTag(info);
497         view.setOnClickListener(info);
498         return view;
499     }
500 
501     /**
502      * Determines when the deferred drag should be started.
503      *
504      * Current behavior:
505      * - Start the drag if the touch passes a certain distance from the original touch down.
506      */
createPreDragCondition(boolean updateIconUi)507     public DragOptions.PreDragCondition createPreDragCondition(boolean updateIconUi) {
508         return new DragOptions.PreDragCondition() {
509 
510             @Override
511             public boolean shouldStartDrag(double distanceDragged) {
512                 return distanceDragged > mStartDragThreshold;
513             }
514 
515             @Override
516             public void onPreDragStart(DropTarget.DragObject dragObject) {
517                 if (!updateIconUi) {
518                     return;
519                 }
520                 if (mIsAboveIcon) {
521                     // Hide only the icon, keep the text visible.
522                     mOriginalIcon.setIconVisible(false);
523                     mOriginalIcon.setVisibility(VISIBLE);
524                 } else {
525                     // Hide both the icon and text.
526                     mOriginalIcon.setVisibility(INVISIBLE);
527                 }
528             }
529 
530             @Override
531             public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
532                 if (!updateIconUi) {
533                     return;
534                 }
535                 mOriginalIcon.setIconVisible(true);
536                 if (dragStarted) {
537                     // Make sure we keep the original icon hidden while it is being dragged.
538                     mOriginalIcon.setVisibility(INVISIBLE);
539                 } else {
540                     // TODO: add WW logging if want to add logging for long press on popup
541                     //  container.
542                     //  mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
543                     if (!mIsAboveIcon) {
544                         // Show the icon but keep the text hidden.
545                         mOriginalIcon.setVisibility(VISIBLE);
546                         mOriginalIcon.setTextVisibility(false);
547                     }
548                 }
549             }
550         };
551     }
552 
553     @Override
554     public void onDropCompleted(View target, DragObject d, boolean success) {  }
555 
556     @Override
557     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
558         // Either the original icon or one of the shortcuts was dragged.
559         // Hide the container, but don't remove it yet because that interferes with touch events.
560         mDeferContainerRemoval = true;
561         animateClose();
562     }
563 
564     @Override
565     public void onDragEnd() {
566         if (!mIsOpen) {
567             if (mOpenCloseAnimator != null) {
568                 // Close animation is running.
569                 mDeferContainerRemoval = false;
570             } else {
571                 // Close animation is not running.
572                 if (mDeferContainerRemoval) {
573                     closeComplete();
574                 }
575             }
576         }
577     }
578 
579     @Override
580     protected void onCreateCloseAnimation(AnimatorSet anim) {
581         // Animate original icon's text back in.
582         anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
583         mOriginalIcon.setForceHideDot(false);
584     }
585 
586     @Override
587     protected void closeComplete() {
588         super.closeComplete();
589         if (mActivityContext.getDragController() != null) {
590             mActivityContext.getDragController().removeDragListener(this);
591         }
592         PopupContainerWithArrow openPopup = getOpen(mActivityContext);
593         if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
594             mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
595             mOriginalIcon.setForceHideDot(false);
596         }
597     }
598 
599     /**
600      * Returns a PopupContainerWithArrow which is already open or null
601      */
602     public static <T extends Context & ActivityContext> PopupContainerWithArrow getOpen(T context) {
603         return getOpenView(context, TYPE_ACTION_POPUP);
604     }
605 
606     /**
607      * Dismisses the popup if it is no longer valid
608      */
609     public static <T extends Context & ActivityContext> void dismissInvalidPopup(T activity) {
610         PopupContainerWithArrow popup = getOpen(activity);
611         if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
612                 || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
613             popup.animateClose();
614         }
615     }
616 
617     /**
618      * Handler to control drag-and-drop for popup items
619      */
620     public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
621 
622     /**
623      * Drag and drop handler for popup items in Launcher activity
624      */
625     public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
626 
627         protected final Point mIconLastTouchPos = new Point();
628         private final Launcher mLauncher;
629         private final PopupContainerWithArrow mContainer;
630 
631         LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
632             mLauncher = launcher;
633             mContainer = container;
634         }
635 
636         @Override
637         public boolean onTouch(View v, MotionEvent ev) {
638             // Touched a shortcut, update where it was touched so we can drag from there on
639             // long click.
640             switch (ev.getAction()) {
641                 case MotionEvent.ACTION_DOWN:
642                 case MotionEvent.ACTION_MOVE:
643                     mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
644                     break;
645             }
646             return false;
647         }
648 
649         @Override
650         public boolean onLongClick(View v) {
651             if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
652             // Return early if not the correct view
653             if (!(v.getParent() instanceof DeepShortcutView)) return false;
654 
655             // Long clicked on a shortcut.
656             DeepShortcutView sv = (DeepShortcutView) v.getParent();
657             sv.setWillDrawIcon(false);
658 
659             // Move the icon to align with the center-top of the touch point
660             Point iconShift = new Point();
661             iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
662             iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
663 
664             DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
665             WorkspaceItemInfo itemInfo = sv.getFinalInfo();
666             itemInfo.container = CONTAINER_SHORTCUTS;
667             DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
668                     mContainer, itemInfo,
669                     new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
670                     new DragOptions());
671             dv.animateShift(-iconShift.x, -iconShift.y);
672 
673             // TODO: support dragging from within folder without having to close it
674             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
675             return false;
676         }
677     }
678 }
679