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