• 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 com.android.launcher3.LauncherSettings.Favorites.CONTAINER_SHORTCUTS;
20 import static com.android.launcher3.Utilities.ATLEAST_P;
21 import static com.android.launcher3.Utilities.squaredHypot;
22 import static com.android.launcher3.Utilities.squaredTouchSlop;
23 import static com.android.launcher3.config.FeatureFlags.ENABLE_MATERIAL_U_POPUP;
24 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS;
25 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS;
26 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
27 
28 import static java.util.Collections.emptyList;
29 
30 import android.animation.AnimatorSet;
31 import android.animation.LayoutTransition;
32 import android.annotation.TargetApi;
33 import android.content.Context;
34 import android.graphics.Point;
35 import android.graphics.PointF;
36 import android.graphics.Rect;
37 import android.os.Build;
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.BaseDraggingActivity;
50 import com.android.launcher3.BubbleTextView;
51 import com.android.launcher3.DragSource;
52 import com.android.launcher3.DropTarget;
53 import com.android.launcher3.DropTarget.DragObject;
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.dot.DotInfo;
59 import com.android.launcher3.dragndrop.DragController;
60 import com.android.launcher3.dragndrop.DragOptions;
61 import com.android.launcher3.dragndrop.DragView;
62 import com.android.launcher3.dragndrop.DraggableView;
63 import com.android.launcher3.model.data.ItemInfo;
64 import com.android.launcher3.model.data.ItemInfoWithIcon;
65 import com.android.launcher3.model.data.WorkspaceItemInfo;
66 import com.android.launcher3.notification.NotificationContainer;
67 import com.android.launcher3.notification.NotificationInfo;
68 import com.android.launcher3.notification.NotificationKeyData;
69 import com.android.launcher3.shortcuts.DeepShortcutView;
70 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
71 import com.android.launcher3.touch.ItemLongClickListener;
72 import com.android.launcher3.util.PackageUserKey;
73 import com.android.launcher3.util.ShortcutUtil;
74 import com.android.launcher3.views.ActivityContext;
75 import com.android.launcher3.views.BaseDragLayer;
76 
77 import java.util.ArrayList;
78 import java.util.List;
79 import java.util.Objects;
80 import java.util.Optional;
81 import java.util.stream.Collectors;
82 
83 /**
84  * A container for shortcuts to deep links and notifications associated with an app.
85  *
86  * @param <T> The activity on with the popup shows
87  */
88 public class PopupContainerWithArrow<T extends Context & ActivityContext>
89         extends ArrowPopup<T> implements DragSource, DragController.DragListener {
90 
91     private final List<DeepShortcutView> mDeepShortcuts = new ArrayList<>();
92     private final PointF mInterceptTouchDown = new PointF();
93 
94     private final int mStartDragThreshold;
95 
96     private static final int SHORTCUT_COLLAPSE_THRESHOLD = 6;
97 
98     private final float mShortcutHeight;
99 
100     private BubbleTextView mOriginalIcon;
101     private int mNumNotifications;
102     private NotificationContainer mNotificationContainer;
103     private int mContainerWidth;
104 
105     private ViewGroup mWidgetContainer;
106     private ViewGroup mDeepShortcutContainer;
107     private ViewGroup mSystemShortcutContainer;
108 
109     protected PopupItemDragHandler mPopupItemDragHandler;
110     protected LauncherAccessibilityDelegate mAccessibilityDelegate;
111 
PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr)112     public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
113         super(context, attrs, defStyleAttr);
114         mStartDragThreshold = getResources().getDimensionPixelSize(
115                 R.dimen.deep_shortcuts_start_drag_threshold);
116         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
117         mShortcutHeight = getResources().getDimension(R.dimen.system_shortcut_header_height);
118     }
119 
PopupContainerWithArrow(Context context, AttributeSet attrs)120     public PopupContainerWithArrow(Context context, AttributeSet attrs) {
121         this(context, attrs, 0);
122     }
123 
PopupContainerWithArrow(Context context)124     public PopupContainerWithArrow(Context context) {
125         this(context, null, 0);
126     }
127 
128     @Override
getAccessibilityInitialFocusView()129     protected View getAccessibilityInitialFocusView() {
130         if (mSystemShortcutContainer != null) {
131             return mSystemShortcutContainer.getChildAt(0);
132         }
133         return super.getAccessibilityInitialFocusView();
134     }
135 
getAccessibilityDelegate()136     public LauncherAccessibilityDelegate getAccessibilityDelegate() {
137         return mAccessibilityDelegate;
138     }
139 
140     @Override
onInterceptTouchEvent(MotionEvent ev)141     public boolean onInterceptTouchEvent(MotionEvent ev) {
142         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
143             mInterceptTouchDown.set(ev.getX(), ev.getY());
144         }
145         if (mNotificationContainer != null
146                 && mNotificationContainer.onInterceptSwipeEvent(ev)) {
147             return true;
148         }
149         // Stop sending touch events to deep shortcut views if user moved beyond touch slop.
150         return squaredHypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY())
151                 > squaredTouchSlop(getContext());
152     }
153 
154     @Override
onTouchEvent(MotionEvent ev)155     public boolean onTouchEvent(MotionEvent ev) {
156         if (mNotificationContainer != null) {
157             return mNotificationContainer.onSwipeEvent(ev) || super.onTouchEvent(ev);
158         }
159         return super.onTouchEvent(ev);
160     }
161 
162     @Override
isOfType(int type)163     protected boolean isOfType(int type) {
164         return (type & TYPE_ACTION_POPUP) != 0;
165     }
166 
getItemClickListener()167     public OnClickListener getItemClickListener() {
168         return (view) -> {
169             mActivityContext.getItemOnClickListener().onClick(view);
170         };
171     }
172 
setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler)173     public void setPopupItemDragHandler(PopupItemDragHandler popupItemDragHandler) {
174         mPopupItemDragHandler = popupItemDragHandler;
175     }
176 
getItemDragHandler()177     public PopupItemDragHandler getItemDragHandler() {
178         return mPopupItemDragHandler;
179     }
180 
181     @Override
onControllerInterceptTouchEvent(MotionEvent ev)182     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
183         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
184             BaseDragLayer dl = getPopupContainer();
185             if (!dl.isEventOverView(this, ev)) {
186                 // TODO: add WW log if want to log if tap closed deep shortcut container.
187                 close(true);
188 
189                 // We let touches on the original icon go through so that users can launch
190                 // the app with one tap if they don't find a shortcut they want.
191                 return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev);
192             }
193         }
194         return false;
195     }
196 
197     @Override
setChildColor(View view, int color, AnimatorSet animatorSetOut)198     protected void setChildColor(View view, int color, AnimatorSet animatorSetOut) {
199         super.setChildColor(view, color, animatorSetOut);
200         if (view.getId() == R.id.notification_container && mNotificationContainer != null) {
201             mNotificationContainer.updateBackgroundColor(color, animatorSetOut);
202         }
203     }
204 
205     /**
206      * Returns true if we can show the container.
207      *
208      * @deprecated Left here since some dependent projects are using this method
209      */
210     @Deprecated
canShow(View icon, ItemInfo item)211     public static boolean canShow(View icon, ItemInfo item) {
212         return icon instanceof BubbleTextView && ShortcutUtil.supportsShortcuts(item);
213     }
214 
215     /**
216      * Shows the notifications and deep shortcuts associated with a Launcher {@param icon}.
217      * @return the container if shown or null.
218      */
showForIcon(BubbleTextView icon)219     public static PopupContainerWithArrow<Launcher> showForIcon(BubbleTextView icon) {
220         Launcher launcher = Launcher.getLauncher(icon.getContext());
221         if (getOpen(launcher) != null) {
222             // There is already an items container open, so don't open this one.
223             icon.clearFocus();
224             return null;
225         }
226         ItemInfo item = (ItemInfo) icon.getTag();
227         if (!ShortcutUtil.supportsShortcuts(item)) {
228             return null;
229         }
230 
231         PopupContainerWithArrow<Launcher> container;
232         PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
233         int deepShortcutCount = popupDataProvider.getShortcutCountForItem(item);
234         List<SystemShortcut> systemShortcuts = launcher.getSupportedShortcuts()
235                 .map(s -> s.getShortcut(launcher, item, icon))
236                 .filter(Objects::nonNull)
237                 .collect(Collectors.toList());
238         if (ENABLE_MATERIAL_U_POPUP.get()) {
239             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
240                     R.layout.popup_container_material_u, launcher.getDragLayer(), false);
241             container.configureForLauncher(launcher);
242             container.populateAndShowRowsMaterialU(icon, deepShortcutCount, systemShortcuts);
243         } else {
244             container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
245                     R.layout.popup_container, launcher.getDragLayer(), false);
246             container.configureForLauncher(launcher);
247             container.populateAndShow(
248                     icon,
249                     deepShortcutCount,
250                     popupDataProvider.getNotificationKeysForItem(item),
251                     systemShortcuts);
252         }
253         launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
254         container.requestFocus();
255         return container;
256     }
257 
configureForLauncher(Launcher launcher)258     private void configureForLauncher(Launcher launcher) {
259         addOnAttachStateChangeListener(new LauncherPopupLiveUpdateHandler(
260                 launcher, (PopupContainerWithArrow<Launcher>) this));
261         mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
262         mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
263         launcher.getDragController().addDragListener(this);
264     }
265 
initializeSystemShortcuts(List<SystemShortcut> shortcuts)266     private void initializeSystemShortcuts(List<SystemShortcut> shortcuts) {
267         if (shortcuts.isEmpty()) {
268             return;
269         }
270         // If there is only 1 shortcut, add it to its own container so it can show text and icon
271         if (shortcuts.size() == 1) {
272             mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_rows_container,
273                     this, 0);
274             initializeSystemShortcut(R.layout.system_shortcut, mSystemShortcutContainer,
275                     shortcuts.get(0), false);
276             return;
277         }
278         addSystemShortcutsIconsOnly(shortcuts);
279     }
280 
281     @TargetApi(Build.VERSION_CODES.P)
populateAndShow(final BubbleTextView originalIcon, int shortcutCount, final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts)282     public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
283             final List<NotificationKeyData> notificationKeys, List<SystemShortcut> shortcuts) {
284         mNumNotifications = notificationKeys.size();
285         mOriginalIcon = originalIcon;
286 
287         boolean hasDeepShortcuts = shortcutCount > 0;
288         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
289 
290         // Add views
291         if (mNumNotifications > 0) {
292             // Add notification entries
293             if (mNotificationContainer == null) {
294                 mNotificationContainer = findViewById(R.id.notification_container);
295                 mNotificationContainer.setVisibility(VISIBLE);
296                 mNotificationContainer.setPopupView(this);
297             } else {
298                 mNotificationContainer.setVisibility(GONE);
299             }
300             updateNotificationHeader();
301         }
302         mSystemShortcutContainer = this;
303         if (mDeepShortcutContainer == null) {
304             mDeepShortcutContainer = findViewById(R.id.deep_shortcuts_container);
305         }
306         if (hasDeepShortcuts) {
307             List<SystemShortcut> systemShortcuts = getNonWidgetSystemShortcuts(shortcuts);
308             // if there are deep shortcuts, we might want to increase the width of shortcuts to fit
309             // horizontally laid out system shortcuts.
310             mContainerWidth = Math.max(mContainerWidth,
311                     systemShortcuts.size() * getResources()
312                             .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size)
313             );
314 
315             mDeepShortcutContainer.setVisibility(View.VISIBLE);
316 
317             for (int i = shortcutCount; i > 0; i--) {
318                 DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut, mDeepShortcutContainer);
319                 v.getLayoutParams().width = mContainerWidth;
320                 mDeepShortcuts.add(v);
321             }
322             updateHiddenShortcuts();
323             Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(shortcuts);
324             if (widgetShortcutOpt.isPresent()) {
325                 if (mWidgetContainer == null) {
326                     mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container, this, 0);
327                 }
328                 initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
329             }
330 
331             initializeSystemShortcuts(systemShortcuts);
332         } else {
333             mDeepShortcutContainer.setVisibility(View.GONE);
334             mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_rows_container,
335                     this, 0);
336             mWidgetContainer = mSystemShortcutContainer;
337             if (!shortcuts.isEmpty()) {
338                 for (int i = 0; i < shortcuts.size(); i++) {
339                     initializeSystemShortcut(
340                             R.layout.system_shortcut,
341                             mSystemShortcutContainer,
342                             shortcuts.get(i),
343                             i < shortcuts.size() - 1);
344                 }
345             }
346         }
347         show();
348         loadAppShortcuts((ItemInfo) originalIcon.getTag(), notificationKeys);
349     }
350 
351     /**
352      * Populate and show shortcuts for the Launcher U app shortcut design.
353      * Will inflate the container and shortcut View instances for the popup container.
354      * @param originalIcon App icon that the popup is shown for
355      * @param deepShortcutCount Number of DeepShortcutView instances to add to container
356      * @param systemShortcuts List of SystemShortcuts to add to container
357      */
populateAndShowRowsMaterialU(final BubbleTextView originalIcon, int deepShortcutCount, List<SystemShortcut> systemShortcuts)358     public void populateAndShowRowsMaterialU(final BubbleTextView originalIcon,
359             int deepShortcutCount, List<SystemShortcut> systemShortcuts) {
360 
361         mOriginalIcon = originalIcon;
362         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
363 
364         if (deepShortcutCount > 0) {
365             addAllShortcutsMaterialU(deepShortcutCount, systemShortcuts);
366         } else if (!systemShortcuts.isEmpty()) {
367             addSystemShortcutsMaterialU(systemShortcuts,
368                     R.layout.system_shortcut_rows_container_material_u,
369                     R.layout.system_shortcut);
370         }
371         show();
372         loadAppShortcuts((ItemInfo) originalIcon.getTag(), /* notificationKeys= */ emptyList());
373     }
374 
375     /**
376      * Animates and loads shortcuts on background thread for this popup container
377      */
loadAppShortcuts(ItemInfo originalItemInfo, List<NotificationKeyData> notificationKeys)378     private void loadAppShortcuts(ItemInfo originalItemInfo,
379             List<NotificationKeyData> notificationKeys) {
380 
381         if (ATLEAST_P) {
382             setAccessibilityPaneTitle(getTitleForAccessibility());
383         }
384         mOriginalIcon.setForceHideDot(true);
385         // All views are added. Animate layout from now on.
386         setLayoutTransition(new LayoutTransition());
387         // Load the shortcuts on a background thread and update the container as it animates.
388         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable(
389                 mActivityContext, originalItemInfo, new Handler(Looper.getMainLooper()),
390                 this, mDeepShortcuts, notificationKeys));
391     }
392 
393     /**
394      * Adds any Deep Shortcuts, System Shortcuts and the Widget Shortcut to their respective
395      * containers
396      * @param deepShortcutCount number of DeepShortcutView instances
397      * @param systemShortcuts List of SystemShortcuts
398      */
addAllShortcutsMaterialU(int deepShortcutCount, List<SystemShortcut> systemShortcuts)399     private void addAllShortcutsMaterialU(int deepShortcutCount,
400             List<SystemShortcut> systemShortcuts) {
401         if (deepShortcutCount + systemShortcuts.size() <= SHORTCUT_COLLAPSE_THRESHOLD) {
402             // add all system shortcuts including widgets shortcut to same container
403             addSystemShortcutsMaterialU(systemShortcuts,
404                     R.layout.system_shortcut_rows_container_material_u,
405                     R.layout.system_shortcut);
406             float currentHeight = (mShortcutHeight * systemShortcuts.size())
407                     + mChildContainerMargin;
408             addDeepShortcutsMaterialU(deepShortcutCount, currentHeight);
409             return;
410         }
411 
412         float currentHeight = mShortcutHeight + mChildContainerMargin;
413         List<SystemShortcut> nonWidgetSystemShortcuts =
414                 getNonWidgetSystemShortcuts(systemShortcuts);
415         // If total shortcuts over threshold, collapse system shortcuts to single row
416         addSystemShortcutsIconsOnly(nonWidgetSystemShortcuts);
417         // May need to recalculate row width
418         mContainerWidth = Math.max(mContainerWidth,
419                 nonWidgetSystemShortcuts.size() * getResources()
420                         .getDimensionPixelSize(R.dimen.system_shortcut_header_icon_touch_size));
421         // Add widget shortcut to separate container
422         Optional<SystemShortcut.Widgets> widgetShortcutOpt = getWidgetShortcut(systemShortcuts);
423         if (widgetShortcutOpt.isPresent()) {
424             mWidgetContainer = inflateAndAdd(R.layout.widget_shortcut_container_material_u,
425                     this);
426             initializeWidgetShortcut(mWidgetContainer, widgetShortcutOpt.get());
427             currentHeight += mShortcutHeight + mChildContainerMargin;
428         }
429         addDeepShortcutsMaterialU(deepShortcutCount, currentHeight);
430     }
431 
432     /**
433      * Finds the first instance of the Widgets Shortcut from the SystemShortcut List
434      * @param systemShortcuts List of SystemShortcut instances to search
435      * @return Optional Widgets SystemShortcut
436      */
getWidgetShortcut( List<SystemShortcut> systemShortcuts)437     private static Optional<SystemShortcut.Widgets> getWidgetShortcut(
438             List<SystemShortcut> systemShortcuts) {
439         return systemShortcuts
440                 .stream()
441                 .filter(shortcut -> shortcut instanceof SystemShortcut.Widgets)
442                 .map(SystemShortcut.Widgets.class::cast)
443                 .findFirst();
444     }
445 
446     /**
447      * Returns list of [systemShortcuts] without the Widgets shortcut instance if found
448      * @param systemShortcuts list of SystemShortcuts to filter from
449      * @return systemShortcuts without the Widgets Shortcut
450      */
getNonWidgetSystemShortcuts( List<SystemShortcut> systemShortcuts)451     private static List<SystemShortcut> getNonWidgetSystemShortcuts(
452             List<SystemShortcut> systemShortcuts) {
453 
454         return systemShortcuts
455                 .stream()
456                 .filter(shortcut -> !(shortcut instanceof SystemShortcut.Widgets))
457                 .collect(Collectors.toList());
458     }
459 
460     /**
461      * Inflates the given systemShortcutContainerLayout as a container, and populates with
462      * the systemShortcuts as views using the systemShortcutLayout
463      * @param systemShortcuts List of SystemShortcut to inflate as Views
464      * @param systemShortcutContainerLayout Layout Resource for the Container of shortcut Views
465      * @param systemShortcutLayout Layout Resource for the individual shortcut Views
466      */
addSystemShortcutsMaterialU(List<SystemShortcut> systemShortcuts, @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout)467     private void addSystemShortcutsMaterialU(List<SystemShortcut> systemShortcuts,
468             @LayoutRes int systemShortcutContainerLayout, @LayoutRes int systemShortcutLayout) {
469 
470         if (systemShortcuts.size() == 0) {
471             return;
472         }
473         mSystemShortcutContainer = inflateAndAdd(systemShortcutContainerLayout, this);
474         mWidgetContainer = mSystemShortcutContainer;
475         for (int i = 0; i < systemShortcuts.size(); i++) {
476             initializeSystemShortcut(
477                     systemShortcutLayout,
478                     mSystemShortcutContainer,
479                     systemShortcuts.get(i),
480                     i < systemShortcuts.size() - 1);
481         }
482     }
483 
addSystemShortcutsIconsOnly(List<SystemShortcut> systemShortcuts)484     private void addSystemShortcutsIconsOnly(List<SystemShortcut> systemShortcuts) {
485         if (systemShortcuts.size() == 0) {
486             return;
487         }
488 
489         mSystemShortcutContainer = ENABLE_MATERIAL_U_POPUP.get()
490                 ? inflateAndAdd(R.layout.system_shortcut_icons_container_material_u, this)
491                 : inflateAndAdd(R.layout.system_shortcut_icons_container, this, 0);
492 
493         for (int i = 0; i < systemShortcuts.size(); i++) {
494             @LayoutRes int shortcutIconLayout = R.layout.system_shortcut_icon_only;
495             boolean shouldAppendSpacer = true;
496 
497             if (i == 0) {
498                 shortcutIconLayout = R.layout.system_shortcut_icon_only_start;
499             } else if (i == systemShortcuts.size() - 1) {
500                 shortcutIconLayout = R.layout.system_shortcut_icon_only_end;
501                 shouldAppendSpacer = false;
502             }
503             initializeSystemShortcut(
504                     shortcutIconLayout,
505                     mSystemShortcutContainer,
506                     systemShortcuts.get(i),
507                     shouldAppendSpacer);
508         }
509     }
510 
511     /**
512      * Inflates and adds [deepShortcutCount] number of DeepShortcutView for the  to a new container
513      * @param deepShortcutCount number of DeepShortcutView instances to add
514      * @param currentHeight height of popup before adding deep shortcuts
515      */
addDeepShortcutsMaterialU(int deepShortcutCount, float currentHeight)516     private void addDeepShortcutsMaterialU(int deepShortcutCount, float currentHeight) {
517         mDeepShortcutContainer = inflateAndAdd(R.layout.deep_shortcut_container, this);
518         for (int i = deepShortcutCount; i > 0; i--) {
519             currentHeight += mShortcutHeight;
520             // when there is limited vertical screen space, limit total popup rows to fit
521             if (currentHeight >= mActivityContext.getDeviceProfile().availableHeightPx) break;
522             DeepShortcutView v = inflateAndAdd(R.layout.deep_shortcut_material_u,
523                     mDeepShortcutContainer);
524             v.getLayoutParams().width = mContainerWidth;
525             mDeepShortcuts.add(v);
526         }
527         updateHiddenShortcuts();
528     }
529 
getNotificationContainer()530     protected NotificationContainer getNotificationContainer() {
531         return mNotificationContainer;
532     }
533 
getOriginalIcon()534     protected BubbleTextView getOriginalIcon() {
535         return mOriginalIcon;
536     }
537 
getSystemShortcutContainer()538     protected ViewGroup getSystemShortcutContainer() {
539         return mSystemShortcutContainer;
540     }
541 
getWidgetContainer()542     protected ViewGroup getWidgetContainer() {
543         return mWidgetContainer;
544     }
545 
setWidgetContainer(ViewGroup widgetContainer)546     protected void setWidgetContainer(ViewGroup widgetContainer) {
547         mWidgetContainer = widgetContainer;
548     }
549 
getTitleForAccessibility()550     private String getTitleForAccessibility() {
551         return getContext().getString(mNumNotifications == 0 ?
552                 R.string.action_deep_shortcut :
553                 R.string.shortcuts_menu_with_notifications_description);
554     }
555 
556     @Override
getTargetObjectLocation(Rect outPos)557     protected void getTargetObjectLocation(Rect outPos) {
558         getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos);
559         outPos.top += mOriginalIcon.getPaddingTop();
560         outPos.left += mOriginalIcon.getPaddingLeft();
561         outPos.right -= mOriginalIcon.getPaddingRight();
562         outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null
563                 ? mOriginalIcon.getIcon().getBounds().height()
564                 : mOriginalIcon.getHeight());
565     }
566 
applyNotificationInfos(List<NotificationInfo> notificationInfos)567     public void applyNotificationInfos(List<NotificationInfo> notificationInfos) {
568         if (mNotificationContainer != null) {
569             mNotificationContainer.applyNotificationInfos(notificationInfos);
570         }
571     }
572 
updateHiddenShortcuts()573     protected void updateHiddenShortcuts() {
574         int allowedCount = mNotificationContainer != null
575                 ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS;
576 
577         int total = mDeepShortcuts.size();
578         for (int i = 0; i < total; i++) {
579             DeepShortcutView view = mDeepShortcuts.get(i);
580             view.setVisibility(i >= allowedCount ? GONE : VISIBLE);
581         }
582     }
583 
initializeWidgetShortcut(ViewGroup container, SystemShortcut info)584     protected void initializeWidgetShortcut(ViewGroup container, SystemShortcut info) {
585         View view = initializeSystemShortcut(R.layout.system_shortcut, container, info, false);
586         view.getLayoutParams().width = mContainerWidth;
587     }
588 
589     /**
590      * Initializes and adds View for given SystemShortcut to a container.
591      * @param resId Resource id to use for SystemShortcut View.
592      * @param container ViewGroup to add the shortcut View to as a parent
593      * @param info The SystemShortcut instance to create a View for.
594      * @param shouldAppendSpacer If True, will add a spacer after the shortcut, when showing the
595      *                        SystemShortcut as an icon only. Used to space the shortcut icons
596      *                        evenly.
597      * @return The view inflated for the SystemShortcut
598      */
initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info, boolean shouldAppendSpacer)599     protected View initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info,
600             boolean shouldAppendSpacer) {
601         View view = inflateAndAdd(resId, container);
602         if (view instanceof DeepShortcutView) {
603             // System shortcut takes entire row with icon and text
604             final DeepShortcutView shortcutView = (DeepShortcutView) view;
605             info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText());
606         } else if (view instanceof ImageView) {
607             // System shortcut is just an icon
608             info.setIconAndContentDescriptionFor((ImageView) view);
609             if (shouldAppendSpacer) inflateAndAdd(R.layout.system_shortcut_spacer, container);
610             view.setTooltipText(view.getContentDescription());
611         }
612         view.setTag(info);
613         view.setOnClickListener(info);
614         return view;
615     }
616 
617     /**
618      * Determines when the deferred drag should be started.
619      *
620      * Current behavior:
621      * - Start the drag if the touch passes a certain distance from the original touch down.
622      */
createPreDragCondition(boolean updateIconUi)623     public DragOptions.PreDragCondition createPreDragCondition(boolean updateIconUi) {
624         return new DragOptions.PreDragCondition() {
625 
626             @Override
627             public boolean shouldStartDrag(double distanceDragged) {
628                 return distanceDragged > mStartDragThreshold;
629             }
630 
631             @Override
632             public void onPreDragStart(DropTarget.DragObject dragObject) {
633                 if (!updateIconUi) {
634                     return;
635                 }
636                 if (mIsAboveIcon) {
637                     // Hide only the icon, keep the text visible.
638                     mOriginalIcon.setIconVisible(false);
639                     mOriginalIcon.setVisibility(VISIBLE);
640                 } else {
641                     // Hide both the icon and text.
642                     mOriginalIcon.setVisibility(INVISIBLE);
643                 }
644             }
645 
646             @Override
647             public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) {
648                 if (!updateIconUi) {
649                     return;
650                 }
651                 mOriginalIcon.setIconVisible(true);
652                 if (dragStarted) {
653                     // Make sure we keep the original icon hidden while it is being dragged.
654                     mOriginalIcon.setVisibility(INVISIBLE);
655                 } else {
656                     // TODO: add WW logging if want to add logging for long press on popup
657                     //  container.
658                     //  mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon);
659                     if (!mIsAboveIcon) {
660                         // Show the icon but keep the text hidden.
661                         mOriginalIcon.setVisibility(VISIBLE);
662                         mOriginalIcon.setTextVisibility(false);
663                     }
664                 }
665             }
666         };
667     }
668 
669     protected void updateNotificationHeader() {
670         ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
671         DotInfo dotInfo = mActivityContext.getDotInfoForItem(itemInfo);
672         if (mNotificationContainer != null && dotInfo != null) {
673             mNotificationContainer.updateHeader(dotInfo.getNotificationCount());
674         }
675     }
676 
677     @Override
678     public void onDropCompleted(View target, DragObject d, boolean success) {  }
679 
680     @Override
681     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
682         // Either the original icon or one of the shortcuts was dragged.
683         // Hide the container, but don't remove it yet because that interferes with touch events.
684         mDeferContainerRemoval = true;
685         animateClose();
686     }
687 
688     @Override
689     public void onDragEnd() {
690         if (!mIsOpen) {
691             if (mOpenCloseAnimator != null) {
692                 // Close animation is running.
693                 mDeferContainerRemoval = false;
694             } else {
695                 // Close animation is not running.
696                 if (mDeferContainerRemoval) {
697                     closeComplete();
698                 }
699             }
700         }
701     }
702 
703     @Override
704     protected void onCreateCloseAnimation(AnimatorSet anim) {
705         // Animate original icon's text back in.
706         anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */));
707         mOriginalIcon.setForceHideDot(false);
708     }
709 
710     @Override
711     protected void closeComplete() {
712         super.closeComplete();
713         if (mActivityContext.getDragController() != null) {
714             mActivityContext.getDragController().removeDragListener(this);
715         }
716         PopupContainerWithArrow openPopup = getOpen(mActivityContext);
717         if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) {
718             mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible());
719             mOriginalIcon.setForceHideDot(false);
720         }
721     }
722 
723     /**
724      * Returns a PopupContainerWithArrow which is already open or null
725      */
726     public static <T extends Context & ActivityContext> PopupContainerWithArrow getOpen(T context) {
727         return getOpenView(context, TYPE_ACTION_POPUP);
728     }
729 
730     /**
731      * Dismisses the popup if it is no longer valid
732      */
733     public static void dismissInvalidPopup(BaseDraggingActivity activity) {
734         PopupContainerWithArrow popup = getOpen(activity);
735         if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
736                 || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
737             popup.animateClose();
738         }
739     }
740 
741     /**
742      * Handler to control drag-and-drop for popup items
743      */
744     public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
745 
746     /**
747      * Drag and drop handler for popup items in Launcher activity
748      */
749     public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
750 
751         protected final Point mIconLastTouchPos = new Point();
752         private final Launcher mLauncher;
753         private final PopupContainerWithArrow mContainer;
754 
755         LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
756             mLauncher = launcher;
757             mContainer = container;
758         }
759 
760         @Override
761         public boolean onTouch(View v, MotionEvent ev) {
762             // Touched a shortcut, update where it was touched so we can drag from there on
763             // long click.
764             switch (ev.getAction()) {
765                 case MotionEvent.ACTION_DOWN:
766                 case MotionEvent.ACTION_MOVE:
767                     mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
768                     break;
769             }
770             return false;
771         }
772 
773         @Override
774         public boolean onLongClick(View v) {
775             if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
776             // Return early if not the correct view
777             if (!(v.getParent() instanceof DeepShortcutView)) return false;
778 
779             // Long clicked on a shortcut.
780             DeepShortcutView sv = (DeepShortcutView) v.getParent();
781             sv.setWillDrawIcon(false);
782 
783             // Move the icon to align with the center-top of the touch point
784             Point iconShift = new Point();
785             iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
786             iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
787 
788             DraggableView draggableView = DraggableView.ofType(DraggableView.DRAGGABLE_ICON);
789             WorkspaceItemInfo itemInfo = sv.getFinalInfo();
790             itemInfo.container = CONTAINER_SHORTCUTS;
791             DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), draggableView,
792                     mContainer, itemInfo,
793                     new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
794                     new DragOptions());
795             dv.animateShift(-iconShift.x, -iconShift.y);
796 
797             // TODO: support dragging from within folder without having to close it
798             AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
799             return false;
800         }
801     }
802 }
803