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