diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-10-09 14:56:21 -0700 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-10-10 10:28:05 -0700 |
commit | 10a1bd0e652ec7ea3e3ee861fc0d72261a33a3fd (patch) | |
tree | e7584efee3c997b6d4f127f910e06c00b303bd16 /src/com | |
parent | 271e219ea3423ada57a55b54395beeef64fb401c (diff) | |
download | android_packages_apps_Trebuchet-10a1bd0e652ec7ea3e3ee861fc0d72261a33a3fd.tar.gz android_packages_apps_Trebuchet-10a1bd0e652ec7ea3e3ee861fc0d72261a33a3fd.tar.bz2 android_packages_apps_Trebuchet-10a1bd0e652ec7ea3e3ee861fc0d72261a33a3fd.zip |
Converting PopupContainerWithArrow into a base class so that it is easier
to create other types of popup
Bug: 67585158
Change-Id: I966ae7bb90f941951b26feaf71b3ea30c3f3c0cc
Diffstat (limited to 'src/com')
9 files changed, 701 insertions, 512 deletions
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 49968189b..0fbad522e 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -38,14 +38,14 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch @IntDef(flag = true, value = { TYPE_FOLDER, - TYPE_POPUP_CONTAINER_WITH_ARROW, + TYPE_ACTION_POPUP, TYPE_WIDGETS_BOTTOM_SHEET, TYPE_WIDGET_RESIZE_FRAME }) @Retention(RetentionPolicy.SOURCE) public @interface FloatingViewType {} public static final int TYPE_FOLDER = 1 << 0; - public static final int TYPE_POPUP_CONTAINER_WITH_ARROW = 1 << 1; + public static final int TYPE_ACTION_POPUP = 1 << 1; public static final int TYPE_WIDGETS_BOTTOM_SHEET = 1 << 2; public static final int TYPE_WIDGET_RESIZE_FRAME = 1 << 3; @@ -138,7 +138,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch } public static AbstractFloatingView getTopOpenView(Launcher launcher) { - return getOpenView(launcher, TYPE_FOLDER | TYPE_POPUP_CONTAINER_WITH_ARROW + return getOpenView(launcher, TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME); } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 2945b2294..1bb4807b5 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -87,7 +87,6 @@ import com.android.launcher3.Workspace.ItemOperator; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; -import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.LauncherAppsCompatVO; @@ -110,6 +109,7 @@ import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.notification.NotificationListener; import com.android.launcher3.pageindicators.PageIndicator; +import com.android.launcher3.popup.BaseActionPopup; import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.popup.PopupDataProvider; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -1347,9 +1347,9 @@ public class Launcher extends BaseActivity mWorkspace.updateIconBadges(updatedBadges); mAppsView.updateIconBadges(updatedBadges); - PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen(Launcher.this); - if (popup != null) { - popup.updateNotificationHeader(updatedBadges); + BaseActionPopup popup = BaseActionPopup.getOpen(Launcher.this); + if (popup instanceof PopupContainerWithArrow) { + ((PopupContainerWithArrow) popup).updateNotificationHeader(updatedBadges); } } }; @@ -3558,7 +3558,7 @@ public class Launcher extends BaseActivity && mAccessibilityDelegate.performAction(focusedView, (ItemInfo) focusedView.getTag(), LauncherAccessibilityDelegate.DEEP_SHORTCUTS)) { - PopupContainerWithArrow.getOpen(this).requestFocus(); + BaseActionPopup.getOpen(this).requestFocus(); return true; } break; diff --git a/src/com/android/launcher3/keyboard/CustomActionsPopup.java b/src/com/android/launcher3/keyboard/CustomActionsPopup.java index 938955ca2..150522ef6 100644 --- a/src/com/android/launcher3/keyboard/CustomActionsPopup.java +++ b/src/com/android/launcher3/keyboard/CustomActionsPopup.java @@ -27,7 +27,7 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.popup.PopupContainerWithArrow; +import com.android.launcher3.popup.BaseActionPopup; import java.util.ArrayList; import java.util.Collections; @@ -46,7 +46,7 @@ public class CustomActionsPopup implements OnMenuItemClickListener { public CustomActionsPopup(Launcher launcher, View icon) { mLauncher = launcher; mIcon = icon; - PopupContainerWithArrow container = PopupContainerWithArrow.getOpen(launcher); + BaseActionPopup container = BaseActionPopup.getOpen(launcher); if (container != null) { mDelegate = container.getAccessibilityDelegate(); } else { diff --git a/src/com/android/launcher3/notification/NotificationFooterLayout.java b/src/com/android/launcher3/notification/NotificationFooterLayout.java index ad07d37bd..3cf3ff62c 100644 --- a/src/com/android/launcher3/notification/NotificationFooterLayout.java +++ b/src/com/android/launcher3/notification/NotificationFooterLayout.java @@ -37,6 +37,7 @@ import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.anim.PropertyResetListener; +import com.android.launcher3.popup.BaseActionPopup; import com.android.launcher3.popup.PopupContainerWithArrow; import java.util.ArrayList; @@ -193,16 +194,17 @@ public class NotificationFooterLayout extends FrameLayout { private void removeViewFromIconRow(View child) { mIconRow.removeView(child); - mNotifications.remove((NotificationInfo) child.getTag()); + mNotifications.remove(child.getTag()); updateOverflowEllipsisVisibility(); if (mIconRow.getChildCount() == 0) { // There are no more icons in the footer, so hide it. - PopupContainerWithArrow popup = PopupContainerWithArrow.getOpen( + BaseActionPopup popup = BaseActionPopup.getOpen( Launcher.getLauncher(getContext())); - if (popup != null) { + if (popup instanceof PopupContainerWithArrow) { final int newHeight = getResources().getDimensionPixelSize( R.dimen.notification_empty_footer_height); - Animator collapseFooter = popup.reduceNotificationViewHeight(getHeight() - newHeight, + Animator collapseFooter = ((PopupContainerWithArrow) popup) + .reduceNotificationViewHeight(getHeight() - newHeight, getResources().getInteger(R.integer.config_removeNotificationViewDuration)); collapseFooter.addListener(new AnimatorListenerAdapter() { @Override diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index 6e36f4f51..8ef10e356 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -31,7 +31,6 @@ import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAppState; import com.android.launcher3.graphics.IconPalette; -import com.android.launcher3.popup.PopupContainerWithArrow; import com.android.launcher3.util.PackageUserKey; /** @@ -110,7 +109,7 @@ public class NotificationInfo implements View.OnClickListener { launcher.getPopupDataProvider().cancelNotification(notificationKey); } AbstractFloatingView.closeOpenContainer(launcher, AbstractFloatingView - .TYPE_POPUP_CONTAINER_WITH_ARROW); + .TYPE_ACTION_POPUP); } public Drawable getIconForBackground(Context context, int background) { diff --git a/src/com/android/launcher3/popup/BaseActionPopup.java b/src/com/android/launcher3/popup/BaseActionPopup.java new file mode 100644 index 000000000..7ffe2ef8b --- /dev/null +++ b/src/com/android/launcher3/popup/BaseActionPopup.java @@ -0,0 +1,599 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.popup; + +import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.CornerPathEffect; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.graphics.drawable.ShapeDrawable; +import android.os.Build; +import android.support.annotation.IntDef; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.widget.TextView; + +import com.android.launcher3.AbstractFloatingView; +import com.android.launcher3.Launcher; +import com.android.launcher3.LauncherAnimUtils; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; +import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; +import com.android.launcher3.anim.PropertyListBuilder; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.graphics.TriangleShape; +import com.android.launcher3.logging.LoggerUtils; +import com.android.launcher3.notification.NotificationItemView; +import com.android.launcher3.shortcuts.DeepShortcutView; +import com.android.launcher3.shortcuts.ShortcutsItemView; +import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import com.android.launcher3.util.PackageUserKey; +import com.android.launcher3.util.Themes; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Set; + +/** + * Base popup container for showing shortcuts to deep links within apps. + */ +@TargetApi(Build.VERSION_CODES.N) +public class BaseActionPopup<V extends TextView> extends AbstractFloatingView { + + public static final int ROUNDED_TOP_CORNERS = 1 << 0; + public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1; + + @IntDef(flag = true, value = { + ROUNDED_TOP_CORNERS, + ROUNDED_BOTTOM_CORNERS + }) + @Retention(RetentionPolicy.SOURCE) + public @interface RoundedCornerFlags {} + + protected final Launcher mLauncher; + protected final LauncherAccessibilityDelegate mAccessibilityDelegate; + private final boolean mIsRtl; + + public ShortcutsItemView mShortcutsItemView; + + protected V mOriginalIcon; + private final Rect mTempRect = new Rect(); + private PointF mInterceptTouchDown = new PointF(); + private boolean mIsLeftAligned; + protected boolean mIsAboveIcon; + protected View mArrow; + private int mGravity; + + protected Animator mOpenCloseAnimator; + protected boolean mDeferContainerRemoval; + private final Rect mStartRect = new Rect(); + private final Rect mEndRect = new Rect(); + + public BaseActionPopup(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mLauncher = Launcher.getLauncher(context); + + mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher); + mIsRtl = Utilities.isRtl(getResources()); + } + + public BaseActionPopup(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public BaseActionPopup(Context context) { + this(context, null, 0); + } + + public LauncherAccessibilityDelegate getAccessibilityDelegate() { + return mAccessibilityDelegate; + } + + protected PopupItemView getItemViewAt(int index) { + if (!mIsAboveIcon) { + // Opening down, so arrow is the first view. + index++; + } + return (PopupItemView) getChildAt(index); + } + + protected int getItemCount() { + // All children except the arrow are items. + return getChildCount() - 1; + } + + protected void animateOpen() { + setVisibility(View.VISIBLE); + mIsOpen = true; + + final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet(); + final Resources res = getResources(); + final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration); + final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); + + // Rectangular reveal. + int itemsTotalHeight = 0; + for (int i = 0; i < getItemCount(); i++) { + itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); + } + Point startPoint = computeAnimStartPoint(itemsTotalHeight); + int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; + float radius = getItemViewAt(0).getBackgroundRadius(); + mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); + mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); + final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider + (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false); + revealAnim.setDuration(revealDuration); + revealAnim.setInterpolator(revealInterpolator); + + Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1); + fadeIn.setDuration(revealDuration); + fadeIn.setInterpolator(revealInterpolator); + openAnim.play(fadeIn); + + // Animate the arrow. + mArrow.setScaleX(0); + mArrow.setScaleY(0); + Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger( + R.integer.config_popupArrowOpenDuration)); + + openAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + Utilities.sendCustomAccessibilityEvent( + BaseActionPopup.this, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.action_deep_shortcut)); + } + }); + + mOpenCloseAnimator = openAnim; + openAnim.playSequentially(revealAnim, arrowScale); + openAnim.start(); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + enforceContainedWithinScreen(l, r); + } + + private void enforceContainedWithinScreen(int left, int right) { + DragLayer dragLayer = mLauncher.getDragLayer(); + if (getTranslationX() + left < 0 || + getTranslationX() + right > dragLayer.getWidth()) { + // If we are still off screen, center horizontally too. + mGravity |= Gravity.CENTER_HORIZONTAL; + } + + if (Gravity.isHorizontal(mGravity)) { + setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2); + } + if (Gravity.isVertical(mGravity)) { + setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2); + } + } + + /** + * Returns the point at which the center of the arrow merges with the first popup item. + */ + private Point computeAnimStartPoint(int itemsTotalHeight) { + int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ? + R.dimen.popup_arrow_horizontal_center_start: + R.dimen.popup_arrow_horizontal_center_end); + if (!mIsLeftAligned) { + arrowCenterX = getMeasuredWidth() - arrowCenterX; + } + int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() + - itemsTotalHeight; + // The y-coordinate of edge between the arrow and the first popup item. + int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight); + return new Point(arrowCenterX, arrowEdge); + } + + /** + * Orients this container above or below the given icon, aligning with the left or right. + * + * These are the preferred orientations, in order (RTL prefers right-aligned over left): + * - Above and left-aligned + * - Above and right-aligned + * - Below and left-aligned + * - Below and right-aligned + * + * So we always align left if there is enough horizontal space + * and align above if there is enough vertical space. + */ + protected void orientAboutIcon(int arrowHeight) { + int width = getMeasuredWidth(); + int height = getMeasuredHeight() + arrowHeight; + + DragLayer dragLayer = mLauncher.getDragLayer(); + dragLayer.getDescendantRectRelativeToSelf(mOriginalIcon, mTempRect); + Rect insets = dragLayer.getInsets(); + + // Align left (right in RTL) if there is room. + int leftAlignedX = mTempRect.left + mOriginalIcon.getPaddingLeft(); + int rightAlignedX = mTempRect.right - width - mOriginalIcon.getPaddingRight(); + int x = leftAlignedX; + boolean canBeLeftAligned = leftAlignedX + width + insets.left + < dragLayer.getRight() - insets.right; + boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left; + if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) { + x = rightAlignedX; + } + mIsLeftAligned = x == leftAlignedX; + if (mIsRtl) { + x -= dragLayer.getWidth() - width; + } + + // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. + int iconWidth = mOriginalIcon.getWidth() + - mOriginalIcon.getTotalPaddingLeft() - mOriginalIcon.getTotalPaddingRight(); + iconWidth *= mOriginalIcon.getScaleX(); + Resources resources = getResources(); + int xOffset; + if (isAlignedWithStart()) { + // Aligning with the shortcut icon. + int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size); + int shortcutPaddingStart = resources.getDimensionPixelSize( + R.dimen.popup_padding_start); + xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart; + } else { + // Aligning with the drag handle. + int shortcutDragHandleWidth = resources.getDimensionPixelSize( + R.dimen.deep_shortcut_drag_handle_size); + int shortcutPaddingEnd = resources.getDimensionPixelSize( + R.dimen.popup_padding_end); + xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd; + } + x += mIsLeftAligned ? xOffset : -xOffset; + + // Open above icon if there is room. + int iconHeight = getIconHeightForPopupPlacement(); + int y = mTempRect.top + mOriginalIcon.getPaddingTop() - height; + mIsAboveIcon = y > dragLayer.getTop() + insets.top; + if (!mIsAboveIcon) { + y = mTempRect.top + mOriginalIcon.getPaddingTop() + iconHeight; + } + + // Insets are added later, so subtract them now. + if (mIsRtl) { + x += insets.right; + } else { + x -= insets.left; + } + y -= insets.top; + + mGravity = 0; + if (y + height > dragLayer.getBottom() - insets.bottom) { + // The container is opening off the screen, so just center it in the drag layer instead. + mGravity = Gravity.CENTER_VERTICAL; + // Put the container next to the icon, preferring the right side in ltr (left in rtl). + int rightSide = leftAlignedX + iconWidth - insets.left; + int leftSide = rightAlignedX - iconWidth - insets.left; + if (!mIsRtl) { + if (rightSide + width < dragLayer.getRight()) { + x = rightSide; + mIsLeftAligned = true; + } else { + x = leftSide; + mIsLeftAligned = false; + } + } else { + if (leftSide > dragLayer.getLeft()) { + x = leftSide; + mIsLeftAligned = false; + } else { + x = rightSide; + mIsLeftAligned = true; + } + } + mIsAboveIcon = true; + } + + setX(x); + setY(y); + } + + protected int getIconHeightForPopupPlacement() { + return mOriginalIcon.getHeight(); + } + + protected boolean isAlignedWithStart() { + return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; + } + + /** + * Adds an arrow view pointing at the original icon. + * @param horizontalOffset the horizontal offset of the arrow, so that it + * points at the center of the original icon + */ + protected View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) { + LayoutParams layoutParams = new LayoutParams(width, height); + if (mIsLeftAligned) { + layoutParams.gravity = Gravity.LEFT; + layoutParams.leftMargin = horizontalOffset; + } else { + layoutParams.gravity = Gravity.RIGHT; + layoutParams.rightMargin = horizontalOffset; + } + if (mIsAboveIcon) { + layoutParams.topMargin = verticalOffset; + } else { + layoutParams.bottomMargin = verticalOffset; + } + + View arrowView = new View(getContext()); + if (Gravity.isVertical(mGravity)) { + // This is only true if there wasn't room for the container next to the icon, + // so we centered it instead. In that case we don't want to show the arrow. + arrowView.setVisibility(INVISIBLE); + } else { + ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( + width, height, !mIsAboveIcon)); + Paint arrowPaint = arrowDrawable.getPaint(); + arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary)); + // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. + int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); + arrowPaint.setPathEffect(new CornerPathEffect(radius)); + arrowView.setBackground(arrowDrawable); + arrowView.setElevation(getElevation()); + } + addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams); + return arrowView; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mInterceptTouchDown.set(ev.getX(), ev.getY()); + return false; + } + // Stop sending touch events to deep shortcut views if user moved beyond touch slop. + return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY()) + > ViewConfiguration.get(getContext()).getScaledTouchSlop(); + } + + protected ObjectAnimator createArrowScaleAnim(float scale) { + return LauncherAnimUtils.ofPropertyValuesHolder( + mArrow, new PropertyListBuilder().scale(scale).build()); + } + + @Override + protected void handleClose(boolean animate) { + if (animate) { + animateClose(); + } else { + closeComplete(); + } + } + + protected void animateClose() { + if (!mIsOpen) { + return; + } + mEndRect.setEmpty(); + if (mOpenCloseAnimator != null) { + Outline outline = new Outline(); + getOutlineProvider().getOutline(this, outline); + outline.getRect(mEndRect); + mOpenCloseAnimator.cancel(); + } + mIsOpen = false; + + final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); + prepareCloseAnimator(closeAnim); + + closeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + if (mDeferContainerRemoval) { + setVisibility(INVISIBLE); + } else { + closeComplete(); + } + } + }); + mOpenCloseAnimator = closeAnim; + closeAnim.start(); + } + + protected void prepareCloseAnimator(AnimatorSet closeAnim) { + final Resources res = getResources(); + final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); + + // Rectangular reveal (reversed). + int itemsTotalHeight = 0; + for (int i = 0; i < getItemCount(); i++) { + itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); + } + Point startPoint = computeAnimStartPoint(itemsTotalHeight); + int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; + float radius = getItemViewAt(0).getBackgroundRadius(); + mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); + if (mEndRect.isEmpty()) { + mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); + } + final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider( + radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true); + revealAnim.setInterpolator(revealInterpolator); + closeAnim.play(revealAnim); + + Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0); + fadeOut.setInterpolator(revealInterpolator); + closeAnim.play(fadeOut); + closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration)); + } + + /** + * Closes the folder without animation. + */ + protected void closeComplete() { + if (mOpenCloseAnimator != null) { + mOpenCloseAnimator.cancel(); + mOpenCloseAnimator = null; + } + mIsOpen = false; + mDeferContainerRemoval = false; + mLauncher.getDragLayer().removeView(this); + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_ACTION_POPUP) != 0; + } + + /** + * Returns a DeepShortcutsContainer which is already open or null + */ + public static BaseActionPopup getOpen(Launcher launcher) { + return getOpenView(launcher, TYPE_ACTION_POPUP); + } + + @Override + public void logActionCommand(int command) { + mLauncher.getUserEventDispatcher().logActionCommand( + command, mOriginalIcon, ContainerType.DEEPSHORTCUTS); + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + DragLayer dl = mLauncher.getDragLayer(); + if (!dl.isEventOverView(this, ev)) { + mLauncher.getUserEventDispatcher().logActionTapOutside( + LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS)); + close(true); + + // We let touches on the original icon go through so that users can launch + // the app with one tap if they don't find a shortcut they want. + return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev); + } + } + return false; + } + + public void populateAndShow(V originalIcon, PopupPopulator.Item[] itemsToPopulate) { + setVisibility(View.INVISIBLE); + mLauncher.getDragLayer().addView(this); + + final Resources resources = getResources(); + final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); + final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); + final int arrowVerticalOffset = resources.getDimensionPixelSize( + R.dimen.popup_arrow_vertical_offset); + + mOriginalIcon = originalIcon; + + // Add dummy views first, and populate with real info when ready. + addDummyViews(itemsToPopulate); + + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + orientAboutIcon(arrowHeight + arrowVerticalOffset); + + boolean reverseOrder = mIsAboveIcon; + if (reverseOrder) { + removeAllViews(); + mShortcutsItemView = null; + itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate); + addDummyViews(itemsToPopulate); + + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + orientAboutIcon(arrowHeight + arrowVerticalOffset); + } + + // Add the arrow. + final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ? + R.dimen.popup_arrow_horizontal_offset_start : + R.dimen.popup_arrow_horizontal_offset_end); + mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight); + mArrow.setPivotX(arrowWidth / 2); + mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight); + + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + animateOpen(); + } + + protected void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate) { + final LayoutInflater inflater = mLauncher.getLayoutInflater(); + int shortcutsItemRoundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS; + int numItems = itemTypesToPopulate.length; + for (int i = 0; i < numItems; i++) { + PopupPopulator.Item itemTypeToPopulate = itemTypesToPopulate[i]; + PopupPopulator.Item prevItemTypeToPopulate = + i > 0 ? itemTypesToPopulate[i - 1] : null; + PopupPopulator.Item nextItemTypeToPopulate = + i < numItems - 1 ? itemTypesToPopulate[i + 1] : null; + final View item = inflater.inflate(itemTypeToPopulate.layoutId, this, false); + + boolean shouldUnroundTopCorners = prevItemTypeToPopulate != null + && itemTypeToPopulate.isShortcut ^ prevItemTypeToPopulate.isShortcut; + boolean shouldUnroundBottomCorners = nextItemTypeToPopulate != null + && itemTypeToPopulate.isShortcut ^ nextItemTypeToPopulate.isShortcut; + + onViewInflated(item, itemTypeToPopulate, + shouldUnroundTopCorners, shouldUnroundBottomCorners); + + if (itemTypeToPopulate.isShortcut) { + if (mShortcutsItemView == null) { + mShortcutsItemView = (ShortcutsItemView) inflater.inflate( + R.layout.shortcuts_item, this, false); + addView(mShortcutsItemView); + if (shouldUnroundTopCorners) { + shortcutsItemRoundedCorners &= ~ROUNDED_TOP_CORNERS; + } + } + mShortcutsItemView.addShortcutView(item, itemTypeToPopulate); + if (shouldUnroundBottomCorners) { + shortcutsItemRoundedCorners &= ~ROUNDED_BOTTOM_CORNERS; + } + } else { + addView(item); + } + } + int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary); + mShortcutsItemView.setBackgroundWithCorners(backgroundColor, shortcutsItemRoundedCorners); + } + + protected void onViewInflated(View view, PopupPopulator.Item itemType, + boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) { + + } +}
\ No newline at end of file diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 5c49b4bdf..68b547d88 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -25,32 +25,17 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; -import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; -import android.graphics.CornerPathEffect; -import android.graphics.Outline; -import android.graphics.Paint; -import android.graphics.Point; -import android.graphics.PointF; -import android.graphics.Rect; -import android.graphics.drawable.ShapeDrawable; import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.support.annotation.IntDef; import android.util.AttributeSet; -import android.view.Gravity; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.View; -import android.view.ViewConfiguration; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateDecelerateInterpolator; -import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; @@ -59,81 +44,42 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherModel; import com.android.launcher3.R; -import com.android.launcher3.Utilities; -import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; -import com.android.launcher3.accessibility.ShortcutMenuAccessibilityDelegate; -import com.android.launcher3.anim.PropertyListBuilder; import com.android.launcher3.anim.PropertyResetListener; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; import com.android.launcher3.badge.BadgeInfo; import com.android.launcher3.dragndrop.DragController; -import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.graphics.IconPalette; -import com.android.launcher3.graphics.TriangleShape; -import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.notification.NotificationItemView; import com.android.launcher3.notification.NotificationKeyData; +import com.android.launcher3.popup.PopupPopulator.Item; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutsItemView; import com.android.launcher3.util.PackageUserKey; import com.android.launcher3.util.Themes; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; /** - * A container for shortcuts to deep links within apps. + * A container for shortcuts to deep links and notifications associated with an app. */ @TargetApi(Build.VERSION_CODES.N) -public class PopupContainerWithArrow extends AbstractFloatingView implements DragSource, +public class PopupContainerWithArrow extends BaseActionPopup<BubbleTextView> implements DragSource, DragController.DragListener { - public static final int ROUNDED_TOP_CORNERS = 1 << 0; - public static final int ROUNDED_BOTTOM_CORNERS = 1 << 1; - - @IntDef(flag = true, value = { - ROUNDED_TOP_CORNERS, - ROUNDED_BOTTOM_CORNERS - }) - @Retention(RetentionPolicy.SOURCE) - public @interface RoundedCornerFlags {} - - protected final Launcher mLauncher; private final int mStartDragThreshold; - private LauncherAccessibilityDelegate mAccessibilityDelegate; - private final boolean mIsRtl; - public ShortcutsItemView mShortcutsItemView; private NotificationItemView mNotificationItemView; - - protected BubbleTextView mOriginalIcon; - private final Rect mTempRect = new Rect(); - private PointF mInterceptTouchDown = new PointF(); - private boolean mIsLeftAligned; - protected boolean mIsAboveIcon; - private View mArrow; - private int mGravity; - - protected Animator mOpenCloseAnimator; - private boolean mDeferContainerRemoval; private AnimatorSet mReduceHeightAnimatorSet; - private final Rect mStartRect = new Rect(); - private final Rect mEndRect = new Rect(); + private int mNumNotifications; public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - mLauncher = Launcher.getLauncher(context); - mStartDragThreshold = getResources().getDimensionPixelSize( R.dimen.deep_shortcuts_start_drag_threshold); - mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher); - mIsRtl = Utilities.isRtl(getResources()); } public PopupContainerWithArrow(Context context, AttributeSet attrs) { @@ -144,10 +90,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra this(context, null, 0); } - public LauncherAccessibilityDelegate getAccessibilityDelegate() { - return mAccessibilityDelegate; - } - /** * Shows the notifications and deep shortcuts associated with {@param icon}. * @return the container if shown or null. @@ -174,49 +116,24 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra final PopupContainerWithArrow container = (PopupContainerWithArrow) launcher.getLayoutInflater().inflate( R.layout.popup_container, launcher.getDragLayer(), false); - container.setVisibility(View.INVISIBLE); - launcher.getDragLayer().addView(container); container.populateAndShow(icon, shortcutIds, notificationKeys, systemShortcuts); return container; } - public void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds, + private void populateAndShow(final BubbleTextView originalIcon, final List<String> shortcutIds, final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) { - final Resources resources = getResources(); - final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); - final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); - final int arrowVerticalOffset = resources.getDimensionPixelSize( - R.dimen.popup_arrow_vertical_offset); - - mOriginalIcon = originalIcon; - - // Add dummy views first, and populate with real info when ready. + mNumNotifications = notificationKeys.size(); PopupPopulator.Item[] itemsToPopulate = PopupPopulator .getItemsToPopulate(shortcutIds, notificationKeys, systemShortcuts); - addDummyViews(itemsToPopulate, notificationKeys.size()); - - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset); - - boolean reverseOrder = mIsAboveIcon; - if (reverseOrder) { - removeAllViews(); - mNotificationItemView = null; - mShortcutsItemView = null; - itemsToPopulate = PopupPopulator.reverseItems(itemsToPopulate); - addDummyViews(itemsToPopulate, notificationKeys.size()); - - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - orientAboutIcon(originalIcon, arrowHeight + arrowVerticalOffset); - } + populateAndShow(originalIcon, itemsToPopulate); ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag(); List<DeepShortcutView> shortcutViews = mShortcutsItemView == null ? Collections.EMPTY_LIST - : mShortcutsItemView.getDeepShortcutViews(reverseOrder); + : mShortcutsItemView.getDeepShortcutViews(mIsAboveIcon); List<View> systemShortcutViews = mShortcutsItemView == null ? Collections.EMPTY_LIST - : mShortcutsItemView.getSystemShortcutViews(reverseOrder); + : mShortcutsItemView.getSystemShortcutViews(mIsAboveIcon); if (mNotificationItemView != null) { updateNotificationHeader(); } @@ -232,17 +149,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra numNotifications, originalIcon.getContentDescription().toString())); } - // Add the arrow. - final int arrowHorizontalOffset = resources.getDimensionPixelSize(isAlignedWithStart() ? - R.dimen.popup_arrow_horizontal_offset_start : - R.dimen.popup_arrow_horizontal_offset_end); - mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight); - mArrow.setPivotX(arrowWidth / 2); - mArrow.setPivotY(mIsAboveIcon ? 0 : arrowHeight); - - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - animateOpen(); - mLauncher.getDragController().addDragListener(this); mOriginalIcon.forceHideBadge(true); @@ -254,6 +160,60 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra systemShortcuts, systemShortcutViews)); } + @Override + protected void addDummyViews(Item[] itemTypesToPopulate) { + mNotificationItemView = null; + super.addDummyViews(itemTypesToPopulate); + if (mNumNotifications > 0) { + mShortcutsItemView.hideShortcuts(mIsAboveIcon, MAX_SHORTCUTS_IF_NOTIFICATIONS); + } + } + + @Override + protected void onViewInflated(View view, Item itemType, + boolean shouldUnroundTopCorners, boolean shouldUnroundBottomCorners) { + if (itemType == PopupPopulator.Item.NOTIFICATION) { + mNotificationItemView = (NotificationItemView) view; + boolean notificationFooterHasIcons = mNumNotifications > 1; + int footerHeight = getResources().getDimensionPixelSize( + notificationFooterHasIcons ? R.dimen.notification_footer_height + : R.dimen.notification_empty_footer_height); + view.findViewById(R.id.footer).getLayoutParams().height = footerHeight; + if (notificationFooterHasIcons) { + mNotificationItemView.findViewById(R.id.divider).setVisibility(VISIBLE); + } + + int roundedCorners = ROUNDED_TOP_CORNERS | ROUNDED_BOTTOM_CORNERS; + if (shouldUnroundTopCorners) { + roundedCorners &= ~ROUNDED_TOP_CORNERS; + mNotificationItemView.findViewById(R.id.gutter_top).setVisibility(VISIBLE); + } + if (shouldUnroundBottomCorners) { + roundedCorners &= ~ROUNDED_BOTTOM_CORNERS; + mNotificationItemView.findViewById(R.id.gutter_bottom).setVisibility(VISIBLE); + } + int backgroundColor = Themes.getAttrColor(mLauncher, R.attr.popupColorTertiary); + mNotificationItemView.setBackgroundWithCorners(backgroundColor, roundedCorners); + + mNotificationItemView.getMainView().setAccessibilityDelegate(mAccessibilityDelegate); + } else if (itemType == PopupPopulator.Item.SHORTCUT) { + view.setAccessibilityDelegate(mAccessibilityDelegate); + } + + if (itemType != PopupPopulator.Item.SYSTEM_SHORTCUT_ICON && itemType.isShortcut + && mNumNotifications > 0) { + int prevHeight = view.getLayoutParams().height; + // Condense shortcuts height when there are notifications. + view.getLayoutParams().height = getResources().getDimensionPixelSize( + R.dimen.bg_popup_item_condensed_height); + if (view instanceof DeepShortcutView) { + float iconScale = (float) view.getLayoutParams().height / prevHeight; + ((DeepShortcutView) view).getIconView().setScaleX(iconScale); + ((DeepShortcutView) view).getIconView().setScaleY(iconScale); + } + } + } + private void addDummyViews(PopupPopulator.Item[] itemTypesToPopulate, int numNotifications) { final Resources res = getResources(); final LayoutInflater inflater = mLauncher.getLayoutInflater(); @@ -337,261 +297,18 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } } - protected PopupItemView getItemViewAt(int index) { - if (!mIsAboveIcon) { - // Opening down, so arrow is the first view. - index++; - } - return (PopupItemView) getChildAt(index); - } - - protected int getItemCount() { - // All children except the arrow are items. - return getChildCount() - 1; - } - - private void animateOpen() { - setVisibility(View.VISIBLE); - mIsOpen = true; - - final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet(); - final Resources res = getResources(); - final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration); - final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); - - // Rectangular reveal. - int itemsTotalHeight = 0; - for (int i = 0; i < getItemCount(); i++) { - itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); - } - Point startPoint = computeAnimStartPoint(itemsTotalHeight); - int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; - float radius = getItemViewAt(0).getBackgroundRadius(); - mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); - mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); - final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider - (radius, radius, mStartRect, mEndRect).createRevealAnimator(this, false); - revealAnim.setDuration(revealDuration); - revealAnim.setInterpolator(revealInterpolator); - - Animator fadeIn = ObjectAnimator.ofFloat(this, ALPHA, 0, 1); - fadeIn.setDuration(revealDuration); - fadeIn.setInterpolator(revealInterpolator); - openAnim.play(fadeIn); - - // Animate the arrow. - mArrow.setScaleX(0); - mArrow.setScaleY(0); - Animator arrowScale = createArrowScaleAnim(1).setDuration(res.getInteger( - R.integer.config_popupArrowOpenDuration)); - - openAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - Utilities.sendCustomAccessibilityEvent( - PopupContainerWithArrow.this, - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.action_deep_shortcut)); - } - }); - - mOpenCloseAnimator = openAnim; - openAnim.playSequentially(revealAnim, arrowScale); - openAnim.start(); - } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - enforceContainedWithinScreen(l, r); - - } - - private void enforceContainedWithinScreen(int left, int right) { - DragLayer dragLayer = mLauncher.getDragLayer(); - if (getTranslationX() + left < 0 || - getTranslationX() + right > dragLayer.getWidth()) { - // If we are still off screen, center horizontally too. - mGravity |= Gravity.CENTER_HORIZONTAL; - } - - if (Gravity.isHorizontal(mGravity)) { - setX(dragLayer.getWidth() / 2 - getMeasuredWidth() / 2); - } - if (Gravity.isVertical(mGravity)) { - setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2); - } - } - - /** - * Returns the point at which the center of the arrow merges with the first popup item. - */ - private Point computeAnimStartPoint(int itemsTotalHeight) { - int arrowCenterX = getResources().getDimensionPixelSize(mIsLeftAligned ^ mIsRtl ? - R.dimen.popup_arrow_horizontal_center_start: - R.dimen.popup_arrow_horizontal_center_end); - if (!mIsLeftAligned) { - arrowCenterX = getMeasuredWidth() - arrowCenterX; - } - int arrowHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - - itemsTotalHeight; - // The y-coordinate of edge between the arrow and the first popup item. - int arrowEdge = getPaddingTop() + (mIsAboveIcon ? itemsTotalHeight : arrowHeight); - return new Point(arrowCenterX, arrowEdge); - } - - /** - * Orients this container above or below the given icon, aligning with the left or right. - * - * These are the preferred orientations, in order (RTL prefers right-aligned over left): - * - Above and left-aligned - * - Above and right-aligned - * - Below and left-aligned - * - Below and right-aligned - * - * So we always align left if there is enough horizontal space - * and align above if there is enough vertical space. - */ - private void orientAboutIcon(BubbleTextView icon, int arrowHeight) { - int width = getMeasuredWidth(); - int height = getMeasuredHeight() + arrowHeight; - - DragLayer dragLayer = mLauncher.getDragLayer(); - dragLayer.getDescendantRectRelativeToSelf(icon, mTempRect); - Rect insets = dragLayer.getInsets(); - - // Align left (right in RTL) if there is room. - int leftAlignedX = mTempRect.left + icon.getPaddingLeft(); - int rightAlignedX = mTempRect.right - width - icon.getPaddingRight(); - int x = leftAlignedX; - boolean canBeLeftAligned = leftAlignedX + width + insets.left - < dragLayer.getRight() - insets.right; - boolean canBeRightAligned = rightAlignedX > dragLayer.getLeft() + insets.left; - if (!canBeLeftAligned || (mIsRtl && canBeRightAligned)) { - x = rightAlignedX; - } - mIsLeftAligned = x == leftAlignedX; - if (mIsRtl) { - x -= dragLayer.getWidth() - width; - } - - // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. - int iconWidth = icon.getWidth() - icon.getTotalPaddingLeft() - icon.getTotalPaddingRight(); - iconWidth *= icon.getScaleX(); - Resources resources = getResources(); - int xOffset; - if (isAlignedWithStart()) { - // Aligning with the shortcut icon. - int shortcutIconWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcut_icon_size); - int shortcutPaddingStart = resources.getDimensionPixelSize( - R.dimen.popup_padding_start); - xOffset = iconWidth / 2 - shortcutIconWidth / 2 - shortcutPaddingStart; - } else { - // Aligning with the drag handle. - int shortcutDragHandleWidth = resources.getDimensionPixelSize( - R.dimen.deep_shortcut_drag_handle_size); - int shortcutPaddingEnd = resources.getDimensionPixelSize( - R.dimen.popup_padding_end); - xOffset = iconWidth / 2 - shortcutDragHandleWidth / 2 - shortcutPaddingEnd; - } - x += mIsLeftAligned ? xOffset : -xOffset; - - // Open above icon if there is room. - int iconHeight = icon.getIcon() != null - ? icon.getIcon().getBounds().height() - : icon.getHeight(); - int y = mTempRect.top + icon.getPaddingTop() - height; - mIsAboveIcon = y > dragLayer.getTop() + insets.top; - if (!mIsAboveIcon) { - y = mTempRect.top + icon.getPaddingTop() + iconHeight; - } - - // Insets are added later, so subtract them now. - if (mIsRtl) { - x += insets.right; - } else { - x -= insets.left; - } - y -= insets.top; - - mGravity = 0; - if (y + height > dragLayer.getBottom() - insets.bottom) { - // The container is opening off the screen, so just center it in the drag layer instead. - mGravity = Gravity.CENTER_VERTICAL; - // Put the container next to the icon, preferring the right side in ltr (left in rtl). - int rightSide = leftAlignedX + iconWidth - insets.left; - int leftSide = rightAlignedX - iconWidth - insets.left; - if (!mIsRtl) { - if (rightSide + width < dragLayer.getRight()) { - x = rightSide; - mIsLeftAligned = true; - } else { - x = leftSide; - mIsLeftAligned = false; - } - } else { - if (leftSide > dragLayer.getLeft()) { - x = leftSide; - mIsLeftAligned = false; - } else { - x = rightSide; - mIsLeftAligned = true; - } - } - mIsAboveIcon = true; + protected void onWidgetsBound() { + if (mShortcutsItemView != null) { + mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon); } - - setX(x); - setY(y); - } - - private boolean isAlignedWithStart() { - return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; } - /** - * Adds an arrow view pointing at the original icon. - * @param horizontalOffset the horizontal offset of the arrow, so that it - * points at the center of the original icon - */ - private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) { - LayoutParams layoutParams = new LayoutParams(width, height); - if (mIsLeftAligned) { - layoutParams.gravity = Gravity.LEFT; - layoutParams.leftMargin = horizontalOffset; - } else { - layoutParams.gravity = Gravity.RIGHT; - layoutParams.rightMargin = horizontalOffset; - } - if (mIsAboveIcon) { - layoutParams.topMargin = verticalOffset; - } else { - layoutParams.bottomMargin = verticalOffset; - } - - View arrowView = new View(getContext()); - if (Gravity.isVertical(mGravity)) { - // This is only true if there wasn't room for the container next to the icon, - // so we centered it instead. In that case we don't want to show the arrow. - arrowView.setVisibility(INVISIBLE); - } else { - ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( - width, height, !mIsAboveIcon)); - Paint arrowPaint = arrowDrawable.getPaint(); - // Note that we have to use getChildAt() instead of getItemViewAt(), - // since the latter expects the arrow which hasn't been added yet. - PopupItemView itemAttachedToArrow = (PopupItemView) - (getChildAt(mIsAboveIcon ? getChildCount() - 1 : 0)); - arrowPaint.setColor(Themes.getAttrColor(mLauncher, R.attr.popupColorPrimary)); - // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable. - int radius = getResources().getDimensionPixelSize(R.dimen.popup_arrow_corner_radius); - arrowPaint.setPathEffect(new CornerPathEffect(radius)); - arrowView.setBackground(arrowDrawable); - arrowView.setElevation(getElevation()); - } - addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams); - return arrowView; + @Override + protected int getIconHeightForPopupPlacement() { + return mOriginalIcon.getIcon() != null + ? mOriginalIcon.getIcon().getBounds().height() + : mOriginalIcon.getHeight(); } /** @@ -638,17 +355,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra }; } - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mInterceptTouchDown.set(ev.getX(), ev.getY()); - return false; - } - // Stop sending touch events to deep shortcut views if user moved beyond touch slop. - return Math.hypot(mInterceptTouchDown.x - ev.getX(), mInterceptTouchDown.y - ev.getY()) - > ViewConfiguration.get(getContext()).getScaledTouchSlop(); - } - /** * Updates the notification header if the original icon's badge updated. */ @@ -719,18 +425,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra badgeInfo.getNotificationKeys())); } - @Override - protected void onWidgetsBound() { - if (mShortcutsItemView != null) { - mShortcutsItemView.enableWidgetsIfExist(mOriginalIcon); - } - } - - private ObjectAnimator createArrowScaleAnim(float scale) { - return LauncherAnimUtils.ofPropertyValuesHolder( - mArrow, new PropertyListBuilder().scale(scale).build()); - } - public Animator reduceNotificationViewHeight(int heightToRemove, int duration) { return adjustItemHeights(heightToRemove, 0, duration); } @@ -832,124 +526,20 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } @Override - protected void handleClose(boolean animate) { - if (animate) { - animateClose(); - } else { - closeComplete(); - } - } - - protected void animateClose() { - if (!mIsOpen) { - return; - } - mEndRect.setEmpty(); - if (mOpenCloseAnimator != null) { - Outline outline = new Outline(); - getOutlineProvider().getOutline(this, outline); - outline.getRect(mEndRect); - mOpenCloseAnimator.cancel(); - } - mIsOpen = false; - - final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); - final Resources res = getResources(); - final long revealDuration = (long) res.getInteger(R.integer.config_popupOpenCloseDuration); - final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); - - // Rectangular reveal (reversed). - int itemsTotalHeight = 0; - for (int i = 0; i < getItemCount(); i++) { - itemsTotalHeight += getItemViewAt(i).getMeasuredHeight(); - } - Point startPoint = computeAnimStartPoint(itemsTotalHeight); - int top = mIsAboveIcon ? getPaddingTop() : startPoint.y; - float radius = getItemViewAt(0).getBackgroundRadius(); - mStartRect.set(startPoint.x, startPoint.y, startPoint.x, startPoint.y); - if (mEndRect.isEmpty()) { - mEndRect.set(0, top, getMeasuredWidth(), top + itemsTotalHeight); - } - final ValueAnimator revealAnim = new RoundedRectRevealOutlineProvider( - radius, radius, mStartRect, mEndRect).createRevealAnimator(this, true); - revealAnim.setDuration(revealDuration); - revealAnim.setInterpolator(revealInterpolator); - closeAnim.play(revealAnim); - - Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0); - fadeOut.setDuration(revealDuration); - fadeOut.setInterpolator(revealInterpolator); - closeAnim.play(fadeOut); - + protected void prepareCloseAnimator(AnimatorSet closeAnim) { // Animate original icon's text back in. - Animator fadeText = mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */); - fadeText.setDuration(revealDuration); - closeAnim.play(fadeText); + closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); - closeAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - if (mDeferContainerRemoval) { - setVisibility(INVISIBLE); - } else { - closeComplete(); - } - } - }); - mOpenCloseAnimator = closeAnim; - closeAnim.start(); mOriginalIcon.forceHideBadge(false); + super.prepareCloseAnimator(closeAnim); } - /** - * Closes the folder without animation. - */ + @Override protected void closeComplete() { - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - mOpenCloseAnimator = null; - } - mIsOpen = false; - mDeferContainerRemoval = false; mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); mOriginalIcon.forceHideBadge(false); - mLauncher.getDragController().removeDragListener(this); - mLauncher.getDragLayer().removeView(this); - } - @Override - protected boolean isOfType(int type) { - return (type & TYPE_POPUP_CONTAINER_WITH_ARROW) != 0; - } - - /** - * Returns a DeepShortcutsContainer which is already open or null - */ - public static PopupContainerWithArrow getOpen(Launcher launcher) { - return getOpenView(launcher, TYPE_POPUP_CONTAINER_WITH_ARROW); - } - - @Override - public void logActionCommand(int command) { - mLauncher.getUserEventDispatcher().logActionCommand( - command, mOriginalIcon, ContainerType.DEEPSHORTCUTS); - } - - @Override - public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - DragLayer dl = mLauncher.getDragLayer(); - if (!dl.isEventOverView(this, ev)) { - mLauncher.getUserEventDispatcher().logActionTapOutside( - LoggerUtils.newContainerTarget(ContainerType.DEEPSHORTCUTS)); - close(true); - - // We let touches on the original icon go through so that users can launch - // the app with one tap if they don't find a shortcut they want. - return mOriginalIcon == null || !dl.isEventOverView(mOriginalIcon, ev); - } - } - return false; + mLauncher.getDragController().removeDragListener(this); + super.closeComplete(); } } diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java index c921b4b82..aeb713479 100644 --- a/src/com/android/launcher3/popup/PopupDataProvider.java +++ b/src/com/android/launcher3/popup/PopupDataProvider.java @@ -102,11 +102,7 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan mPackageUserToBadgeInfos.remove(removedPackageUserKey); } updateLauncherIconBadges(Utilities.singletonHashSet(removedPackageUserKey)); - - PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); - if (openContainer != null) { - openContainer.trimNotifications(mPackageUserToBadgeInfos); - } + trimNotifications(mPackageUserToBadgeInfos); } } @@ -143,10 +139,13 @@ public class PopupDataProvider implements NotificationListener.NotificationsChan if (!updatedBadges.isEmpty()) { updateLauncherIconBadges(updatedBadges.keySet()); } + trimNotifications(updatedBadges); + } - PopupContainerWithArrow openContainer = PopupContainerWithArrow.getOpen(mLauncher); - if (openContainer != null) { - openContainer.trimNotifications(updatedBadges); + private void trimNotifications(Map<PackageUserKey, BadgeInfo> updatedBadges) { + BaseActionPopup openContainer = BaseActionPopup.getOpen(mLauncher); + if (openContainer instanceof PopupContainerWithArrow) { + ((PopupContainerWithArrow) openContainer).trimNotifications(updatedBadges); } } diff --git a/src/com/android/launcher3/popup/PopupItemView.java b/src/com/android/launcher3/popup/PopupItemView.java index 8ec051b30..75c3f26cf 100644 --- a/src/com/android/launcher3/popup/PopupItemView.java +++ b/src/com/android/launcher3/popup/PopupItemView.java @@ -32,7 +32,7 @@ import android.widget.FrameLayout; import com.android.launcher3.R; import com.android.launcher3.Utilities; -import com.android.launcher3.popup.PopupContainerWithArrow.RoundedCornerFlags; +import com.android.launcher3.popup.BaseActionPopup.RoundedCornerFlags; import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_BOTTOM_CORNERS; import static com.android.launcher3.popup.PopupContainerWithArrow.ROUNDED_TOP_CORNERS; |