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.Utilities.squaredHypot; 20 import static com.android.launcher3.Utilities.squaredTouchSlop; 21 import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO; 22 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS; 23 import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS; 24 import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; 25 import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; 26 import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; 27 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 28 29 import android.animation.AnimatorSet; 30 import android.animation.LayoutTransition; 31 import android.annotation.TargetApi; 32 import android.content.Context; 33 import android.graphics.Point; 34 import android.graphics.PointF; 35 import android.graphics.Rect; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.Looper; 39 import android.util.AttributeSet; 40 import android.util.Log; 41 import android.util.Pair; 42 import android.view.MotionEvent; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.widget.ImageView; 46 47 import com.android.launcher3.AbstractFloatingView; 48 import com.android.launcher3.BubbleTextView; 49 import com.android.launcher3.DragSource; 50 import com.android.launcher3.DropTarget; 51 import com.android.launcher3.DropTarget.DragObject; 52 import com.android.launcher3.ItemInfo; 53 import com.android.launcher3.ItemInfoWithIcon; 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.logging.LoggerUtils; 63 import com.android.launcher3.notification.NotificationInfo; 64 import com.android.launcher3.notification.NotificationItemView; 65 import com.android.launcher3.notification.NotificationKeyData; 66 import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener; 67 import com.android.launcher3.shortcuts.DeepShortcutView; 68 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; 69 import com.android.launcher3.testing.TestProtocol; 70 import com.android.launcher3.touch.ItemClickHandler; 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.BaseDragLayer; 75 76 import java.util.ArrayList; 77 import java.util.List; 78 import java.util.Map; 79 import java.util.function.Predicate; 80 81 /** 82 * A container for shortcuts to deep links and notifications associated with an app. 83 */ 84 public class PopupContainerWithArrow extends ArrowPopup implements DragSource, 85 DragController.DragListener, View.OnLongClickListener, 86 View.OnTouchListener, PopupDataChangeListener { 87 88 private final List<DeepShortcutView> mShortcuts = new ArrayList<>(); 89 private final PointF mInterceptTouchDown = new PointF(); 90 protected final Point mIconLastTouchPos = new Point(); 91 92 private final int mStartDragThreshold; 93 private final LauncherAccessibilityDelegate mAccessibilityDelegate; 94 95 private BubbleTextView mOriginalIcon; 96 private NotificationItemView mNotificationItemView; 97 private int mNumNotifications; 98 99 private ViewGroup mSystemShortcutContainer; 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 mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher); 106 } 107 PopupContainerWithArrow(Context context, AttributeSet attrs)108 public PopupContainerWithArrow(Context context, AttributeSet attrs) { 109 this(context, attrs, 0); 110 } 111 PopupContainerWithArrow(Context context)112 public PopupContainerWithArrow(Context context) { 113 this(context, null, 0); 114 } 115 getAccessibilityDelegate()116 public LauncherAccessibilityDelegate getAccessibilityDelegate() { 117 return mAccessibilityDelegate; 118 } 119 120 @Override onAttachedToWindow()121 protected void onAttachedToWindow() { 122 super.onAttachedToWindow(); 123 mLauncher.getPopupDataProvider().setChangeListener(this); 124 } 125 126 @Override onDetachedFromWindow()127 protected void onDetachedFromWindow() { 128 super.onDetachedFromWindow(); 129 mLauncher.getPopupDataProvider().setChangeListener(null); 130 } 131 132 @Override onInterceptTouchEvent(MotionEvent ev)133 public boolean onInterceptTouchEvent(MotionEvent ev) { 134 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 135 mInterceptTouchDown.set(ev.getX(), ev.getY()); 136 } 137 if (mNotificationItemView != null 138 && mNotificationItemView.onInterceptTouchEvent(ev)) { 139 return true; 140 } 141 // Stop sending touch events to deep shortcut views if user moved beyond touch slop. 142 return squaredHypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY()) 143 > squaredTouchSlop(getContext()); 144 } 145 146 @Override onTouchEvent(MotionEvent ev)147 public boolean onTouchEvent(MotionEvent ev) { 148 if (mNotificationItemView != null) { 149 return mNotificationItemView.onTouchEvent(ev) || super.onTouchEvent(ev); 150 } 151 return super.onTouchEvent(ev); 152 } 153 154 @Override isOfType(int type)155 protected boolean isOfType(int type) { 156 return (type & TYPE_ACTION_POPUP) != 0; 157 } 158 159 @Override logActionCommand(int command)160 public void logActionCommand(int command) { 161 mLauncher.getUserEventDispatcher().logActionCommand( 162 command, mOriginalIcon, getLogContainerType()); 163 } 164 165 @Override getLogContainerType()166 public int getLogContainerType() { 167 return ContainerType.DEEPSHORTCUTS; 168 } 169 getItemClickListener()170 public OnClickListener getItemClickListener() { 171 return (view) -> { 172 ItemClickHandler.INSTANCE.onClick(view); 173 close(true); 174 }; 175 } 176 177 @Override onControllerInterceptTouchEvent(MotionEvent ev)178 public boolean onControllerInterceptTouchEvent(MotionEvent ev) { 179 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 180 BaseDragLayer dl = getPopupContainer(); 181 if (!dl.isEventOverView(this, ev)) { 182 mLauncher.getUserEventDispatcher().logActionTapOutside( 183 LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS)); 184 close(true); 185 186 // We let touches on the original icon go through so that users can launch 187 // the app with one tap if they don't find a shortcut they want. 188 return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev); 189 } 190 } 191 return false; 192 } 193 194 /** 195 * Shows the notifications and deep shortcuts associated with {@param icon}. 196 * @return the container if shown or null. 197 */ showForIcon(BubbleTextView icon)198 public static PopupContainerWithArrow showForIcon(BubbleTextView icon) { 199 if (TestProtocol.sDebugTracing) { 200 Log.d(TestProtocol.NO_CONTEXT_MENU, "showForIcon"); 201 } 202 Launcher launcher = Launcher.getLauncher(icon.getContext()); 203 if (getOpen(launcher) != null) { 204 // There is already an items container open, so don't open this one. 205 icon.clearFocus(); 206 return null; 207 } 208 ItemInfo itemInfo = (ItemInfo) icon.getTag(); 209 if (!ShortcutUtil.supportsShortcuts(itemInfo)) { 210 return null; 211 } 212 213 final PopupContainerWithArrow container = 214 (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( 215 R.layout.popup_container, launcher.getDragLayer(), false); 216 container.populateAndShow(icon, itemInfo, SystemShortcutFactory.INSTANCE.get(launcher)); 217 return container; 218 } 219 220 @Override onInflationComplete(boolean isReversed)221 protected void onInflationComplete(boolean isReversed) { 222 if (isReversed && mNotificationItemView != null) { 223 mNotificationItemView.inverseGutterMargin(); 224 } 225 226 // Update dividers 227 int count = getChildCount(); 228 DeepShortcutView lastView = null; 229 for (int i = 0; i < count; i++) { 230 View view = getChildAt(i); 231 if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) { 232 if (lastView != null) { 233 lastView.setDividerVisibility(VISIBLE); 234 } 235 lastView = (DeepShortcutView) view; 236 lastView.setDividerVisibility(INVISIBLE); 237 } 238 } 239 } 240 populateAndShow( BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory)241 protected void populateAndShow( 242 BubbleTextView icon, ItemInfo item, SystemShortcutFactory factory) { 243 if (TestProtocol.sDebugTracing) { 244 Log.d(TestProtocol.NO_CONTEXT_MENU, "populateAndShow"); 245 } 246 PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider(); 247 populateAndShow(icon, 248 popupDataProvider.getShortcutCountForItem(item), 249 popupDataProvider.getNotificationKeysForItem(item), 250 factory.getEnabledShortcuts(mLauncher, item)); 251 } 252 getSystemShortcutContainerForTesting()253 public ViewGroup getSystemShortcutContainerForTesting() { 254 return mSystemShortcutContainer; 255 } 256 257 @TargetApi(Build.VERSION_CODES.P) populateAndShow(final BubbleTextView originalIcon, int shortcutCount, final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts)258 protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount, 259 final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) { 260 mNumNotifications = notificationKeys.size(); 261 mOriginalIcon = originalIcon; 262 263 // Add views 264 if (mNumNotifications > 0) { 265 // Add notification entries 266 View.inflate(getContext(), R.layout.notification_content, this); 267 mNotificationItemView = new NotificationItemView(this); 268 if (mNumNotifications == 1) { 269 mNotificationItemView.removeFooter(); 270 } 271 updateNotificationHeader(); 272 } 273 int viewsToFlip = getChildCount(); 274 mSystemShortcutContainer = this; 275 276 if (shortcutCount > 0) { 277 if (mNotificationItemView != null) { 278 mNotificationItemView.addGutter(); 279 } 280 281 for (int i = shortcutCount; i > 0; i--) { 282 mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this)); 283 } 284 updateHiddenShortcuts(); 285 286 if (!systemShortcuts.isEmpty()) { 287 mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this); 288 for (SystemShortcut shortcut : systemShortcuts) { 289 initializeSystemShortcut( 290 R.layout.system_shortcut_icon_only, mSystemShortcutContainer, shortcut); 291 } 292 } 293 } else if (!systemShortcuts.isEmpty()) { 294 if (mNotificationItemView != null) { 295 mNotificationItemView.addGutter(); 296 } 297 298 for (SystemShortcut shortcut : systemShortcuts) { 299 initializeSystemShortcut(R.layout.system_shortcut, this, shortcut); 300 } 301 } 302 303 reorderAndShow(viewsToFlip); 304 305 ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag(); 306 if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 307 setAccessibilityPaneTitle(getTitleForAccessibility()); 308 } 309 310 mLauncher.getDragController().addDragListener(this); 311 mOriginalIcon.setForceHideDot(true); 312 313 // All views are added. Animate layout from now on. 314 setLayoutTransition(new LayoutTransition()); 315 316 // Load the shortcuts on a background thread and update the container as it animates. 317 MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(PopupPopulator.createUpdateRunnable( 318 mLauncher, originalItemInfo, new Handler(Looper.getMainLooper()), 319 this, mShortcuts, notificationKeys)); 320 } 321 getTitleForAccessibility()322 private String getTitleForAccessibility() { 323 return getContext().getString(mNumNotifications == 0 ? 324 R.string.action_deep_shortcut : 325 R.string.shortcuts_menu_with_notifications_description); 326 } 327 328 @Override getAccessibilityTarget()329 protected Pair<View, String> getAccessibilityTarget() { 330 return Pair.create(this, ""); 331 } 332 333 @Override getTargetObjectLocation(Rect outPos)334 protected void getTargetObjectLocation(Rect outPos) { 335 getPopupContainer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos); 336 outPos.top += mOriginalIcon.getPaddingTop(); 337 outPos.left += mOriginalIcon.getPaddingLeft(); 338 outPos.right -= mOriginalIcon.getPaddingRight(); 339 outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null 340 ? mOriginalIcon.getIcon().getBounds().height() 341 : mOriginalIcon.getHeight()); 342 } 343 applyNotificationInfos(List<NotificationInfo> notificationInfos)344 public void applyNotificationInfos(List<NotificationInfo> notificationInfos) { 345 mNotificationItemView.applyNotificationInfos(notificationInfos); 346 } 347 updateHiddenShortcuts()348 private void updateHiddenShortcuts() { 349 int allowedCount = mNotificationItemView != null 350 ? MAX_SHORTCUTS_IF_NOTIFICATIONS : MAX_SHORTCUTS; 351 int originalHeight = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_height); 352 int itemHeight = mNotificationItemView != null ? 353 getResources().getDimensionPixelSize(R.dimen.bg_popup_item_condensed_height) 354 : originalHeight; 355 float iconScale = ((float) itemHeight) / originalHeight; 356 357 int total = mShortcuts.size(); 358 for (int i = 0; i < total; i++) { 359 DeepShortcutView view = mShortcuts.get(i); 360 view.setVisibility(i >= allowedCount ? GONE : VISIBLE); 361 view.getLayoutParams().height = itemHeight; 362 view.getIconView().setScaleX(iconScale); 363 view.getIconView().setScaleY(iconScale); 364 } 365 } 366 updateDividers()367 private void updateDividers() { 368 int count = getChildCount(); 369 DeepShortcutView lastView = null; 370 for (int i = 0; i < count; i++) { 371 View view = getChildAt(i); 372 if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) { 373 if (lastView != null) { 374 lastView.setDividerVisibility(VISIBLE); 375 } 376 lastView = (DeepShortcutView) view; 377 lastView.setDividerVisibility(INVISIBLE); 378 } 379 } 380 } 381 382 @Override onWidgetsBound()383 public void onWidgetsBound() { 384 ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); 385 SystemShortcut widgetInfo = new SystemShortcut.Widgets(); 386 View.OnClickListener onClickListener = widgetInfo.getOnClickListener(mLauncher, itemInfo); 387 View widgetsView = null; 388 int count = mSystemShortcutContainer.getChildCount(); 389 for (int i = 0; i < count; i++) { 390 View systemShortcutView = mSystemShortcutContainer.getChildAt(i); 391 if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) { 392 widgetsView = systemShortcutView; 393 break; 394 } 395 } 396 397 if (onClickListener != null && widgetsView == null) { 398 // We didn't have any widgets cached but now there are some, so enable the shortcut. 399 if (mSystemShortcutContainer != this) { 400 initializeSystemShortcut( 401 R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo); 402 } else { 403 // If using the expanded system shortcut (as opposed to just the icon), we need to 404 // reopen the container to ensure measurements etc. all work out. While this could 405 // be quite janky, in practice the user would typically see a small flicker as the 406 // animation restarts partway through, and this is a very rare edge case anyway. 407 close(false); 408 PopupContainerWithArrow.showForIcon(mOriginalIcon); 409 } 410 } else if (onClickListener == null && widgetsView != null) { 411 // No widgets exist, but we previously added the shortcut so remove it. 412 if (mSystemShortcutContainer != this) { 413 mSystemShortcutContainer.removeView(widgetsView); 414 } else { 415 close(false); 416 PopupContainerWithArrow.showForIcon(mOriginalIcon); 417 } 418 } 419 } 420 initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info)421 private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) { 422 View view = inflateAndAdd( 423 resId, container, getInsertIndexForSystemShortcut(container, info)); 424 if (view instanceof DeepShortcutView) { 425 // Expanded system shortcut, with both icon and text shown on white background. 426 final DeepShortcutView shortcutView = (DeepShortcutView) view; 427 info.setIconAndLabelFor(shortcutView.getIconView(), shortcutView.getBubbleText()); 428 } else if (view instanceof ImageView) { 429 // Only the system shortcut icon shows on a gray background header. 430 info.setIconAndContentDescriptionFor((ImageView) view); 431 } 432 view.setTag(info); 433 view.setOnClickListener(info.getOnClickListener(mLauncher, 434 (ItemInfo) mOriginalIcon.getTag())); 435 } 436 437 /** 438 * Returns an index for inserting a shortcut into a container. 439 */ getInsertIndexForSystemShortcut(ViewGroup container, SystemShortcut shortcut)440 private int getInsertIndexForSystemShortcut(ViewGroup container, SystemShortcut shortcut) { 441 final View separator = container.findViewById(R.id.separator); 442 443 return separator != null && shortcut.isLeftGroup() ? 444 container.indexOfChild(separator) : 445 container.getChildCount(); 446 } 447 448 /** 449 * Determines when the deferred drag should be started. 450 * 451 * Current behavior: 452 * - Start the drag if the touch passes a certain distance from the original touch down. 453 */ createPreDragCondition()454 public DragOptions.PreDragCondition createPreDragCondition() { 455 return new DragOptions.PreDragCondition() { 456 457 @Override 458 public boolean shouldStartDrag(double distanceDragged) { 459 return distanceDragged > mStartDragThreshold; 460 } 461 462 @Override 463 public void onPreDragStart(DropTarget.DragObject dragObject) { 464 if (mIsAboveIcon) { 465 // Hide only the icon, keep the text visible. 466 mOriginalIcon.setIconVisible(false); 467 mOriginalIcon.setVisibility(VISIBLE); 468 } else { 469 // Hide both the icon and text. 470 mOriginalIcon.setVisibility(INVISIBLE); 471 } 472 } 473 474 @Override 475 public void onPreDragEnd(DropTarget.DragObject dragObject, boolean dragStarted) { 476 mOriginalIcon.setIconVisible(true); 477 if (dragStarted) { 478 // Make sure we keep the original icon hidden while it is being dragged. 479 mOriginalIcon.setVisibility(INVISIBLE); 480 } else { 481 mLauncher.getUserEventDispatcher().logDeepShortcutsOpen(mOriginalIcon); 482 if (!mIsAboveIcon) { 483 // Show the icon but keep the text hidden. 484 mOriginalIcon.setVisibility(VISIBLE); 485 mOriginalIcon.setTextVisibility(false); 486 } 487 } 488 } 489 }; 490 } 491 492 /** 493 * Updates the notification header if the original icon's dot updated. 494 */ 495 @Override 496 public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) { 497 ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); 498 PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo); 499 if (updatedDots.test(packageUser)) { 500 updateNotificationHeader(); 501 } 502 } 503 504 private void updateNotificationHeader() { 505 ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag(); 506 DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo); 507 if (mNotificationItemView != null && dotInfo != null) { 508 mNotificationItemView.updateHeader( 509 dotInfo.getNotificationCount(), itemInfo.iconColor); 510 } 511 } 512 513 @Override 514 public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) { 515 if (mNotificationItemView == null) { 516 return; 517 } 518 ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag(); 519 DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo)); 520 if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) { 521 // No more notifications, remove the notification views and expand all shortcuts. 522 mNotificationItemView.removeAllViews(); 523 mNotificationItemView = null; 524 updateHiddenShortcuts(); 525 updateDividers(); 526 } else { 527 mNotificationItemView.trimNotifications( 528 NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys())); 529 } 530 } 531 532 @Override 533 public void onDropCompleted(View target, DragObject d, boolean success) { } 534 535 @Override 536 public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { 537 // Either the original icon or one of the shortcuts was dragged. 538 // Hide the container, but don't remove it yet because that interferes with touch events. 539 mDeferContainerRemoval = true; 540 animateClose(); 541 } 542 543 @Override 544 public void onDragEnd() { 545 if (!mIsOpen) { 546 if (mOpenCloseAnimator != null) { 547 // Close animation is running. 548 mDeferContainerRemoval = false; 549 } else { 550 // Close animation is not running. 551 if (mDeferContainerRemoval) { 552 closeComplete(); 553 } 554 } 555 } 556 } 557 558 @Override 559 public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) { 560 if (info == NOTIFICATION_ITEM_INFO) { 561 target.itemType = ItemType.NOTIFICATION; 562 } else { 563 target.itemType = ItemType.DEEPSHORTCUT; 564 target.rank = info.rank; 565 } 566 targetParent.containerType = ContainerType.DEEPSHORTCUTS; 567 } 568 569 @Override 570 protected void onCreateCloseAnimation(AnimatorSet anim) { 571 // Animate original icon's text back in. 572 anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); 573 mOriginalIcon.setForceHideDot(false); 574 } 575 576 @Override 577 protected void closeComplete() { 578 PopupContainerWithArrow openPopup = getOpen(mLauncher); 579 if (openPopup == null || openPopup.mOriginalIcon != mOriginalIcon) { 580 mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); 581 mOriginalIcon.setForceHideDot(false); 582 } 583 super.closeComplete(); 584 } 585 586 @Override 587 public boolean onTouch(View v, MotionEvent ev) { 588 // Touched a shortcut, update where it was touched so we can drag from there on long click. 589 switch (ev.getAction()) { 590 case MotionEvent.ACTION_DOWN: 591 case MotionEvent.ACTION_MOVE: 592 mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY()); 593 break; 594 } 595 return false; 596 } 597 598 @Override 599 public boolean onLongClick(View v) { 600 if (!ItemLongClickListener.canStartDrag(mLauncher)) return false; 601 // Return early if not the correct view 602 if (!(v.getParent() instanceof DeepShortcutView)) return false; 603 604 // Long clicked on a shortcut. 605 DeepShortcutView sv = (DeepShortcutView) v.getParent(); 606 sv.setWillDrawIcon(false); 607 608 // Move the icon to align with the center-top of the touch point 609 Point iconShift = new Point(); 610 iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x; 611 iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx; 612 613 DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(), 614 this, sv.getFinalInfo(), 615 new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions()); 616 dv.animateShift(-iconShift.x, -iconShift.y); 617 618 // TODO: support dragging from within folder without having to close it 619 AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER); 620 return false; 621 } 622 623 /** 624 * Returns a PopupContainerWithArrow which is already open or null 625 */ 626 public static PopupContainerWithArrow getOpen(Launcher launcher) { 627 return getOpenView(launcher, TYPE_ACTION_POPUP); 628 } 629 } 630