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