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