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