From 2fd7a8bc596f6ec28098b8566203d3b552ac2d39 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Fri, 30 Mar 2018 17:10:13 -0700 Subject: [PATCH] Updating the UI of the options popup to make it look similar to icon popup Bug: 77327164 Change-Id: I3580df8bf8a43cb44123f203ffed9a85fa33aea7 --- res/layout/longpress_options_menu.xml | 84 +-- res/values/dimens.xml | 1 + .../launcher3/BaseDraggingActivity.java | 2 +- src/com/android/launcher3/Launcher.java | 26 +- src/com/android/launcher3/Workspace.java | 5 + .../notification/NotificationItemView.java | 3 +- .../android/launcher3/popup/ArrowPopup.java | 470 +++++++++++++++++ .../popup/PopupContainerWithArrow.java | 480 ++---------------- .../touch/WorkspaceTouchListener.java | 2 +- .../views/LauncherDragIndicator.java | 9 +- .../launcher3/views/OptionsPopupView.java | 293 ++++------- 11 files changed, 638 insertions(+), 737 deletions(-) create mode 100644 src/com/android/launcher3/popup/ArrowPopup.java diff --git a/res/layout/longpress_options_menu.xml b/res/layout/longpress_options_menu.xml index 71d117af86..168dbc3a2e 100644 --- a/res/layout/longpress_options_menu.xml +++ b/res/layout/longpress_options_menu.xml @@ -15,83 +15,11 @@ --> - - - - - - - - - - - - - - - - - - - \ No newline at end of file + android:orientation="vertical" /> diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a2f7286794..b1ad11ef05 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -222,4 +222,5 @@ 24dp + 32dp diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java index 4c11fe66e0..bde9ad3109 100644 --- a/src/com/android/launcher3/BaseDraggingActivity.java +++ b/src/com/android/launcher3/BaseDraggingActivity.java @@ -47,7 +47,7 @@ public abstract class BaseDraggingActivity extends BaseActivity private static final String TAG = "BaseDraggingActivity"; // The Intent extra that defines whether to ignore the launch animation - protected static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = + public static final String INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION = "com.android.launcher3.intent.extra.shortcut.INGORE_LAUNCH_ANIMATION"; // When starting an action mode, setting this tag will cause the action mode to be cancelled diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index e2f748837c..ccc774a9ec 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1634,30 +1634,6 @@ public class Launcher extends BaseDraggingActivity } } - /** - * Event handler for the wallpaper picker button that appears after a long press - * on the home screen. - */ - public void onClickWallpaperPicker(View v) { - if (!Utilities.isWallpaperAllowed(this)) { - Toast.makeText(this, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show(); - return; - } - int pageScroll = mWorkspace.getScrollForPage(mWorkspace.getPageNearestToCenterOfScreen()); - float offset = mWorkspace.mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); - Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER) - .putExtra(Utilities.EXTRA_WALLPAPER_OFFSET, offset); - - String pickerPackage = getString(R.string.wallpaper_picker_package); - if (!TextUtils.isEmpty(pickerPackage)) { - intent.setPackage(pickerPackage); - } else { - // If there is no target package, use the default intent chooser animation - intent.putExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); - } - startActivitySafely(v, intent, null); - } - @TargetApi(Build.VERSION_CODES.M) @Override public ActivityOptions getActivityLaunchOptions(View v, boolean useDefaultLaunchOptions) { @@ -2415,7 +2391,7 @@ public class Launcher extends BaseDraggingActivity // Setting the touch point to (-1, -1) will show the options popup in the center of // the screen. - OptionsPopupView.show(this, -1, -1); + OptionsPopupView.showDefaultOptions(this, -1, -1); } return true; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 34ae8ea3d1..a208c7a54c 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -349,6 +349,11 @@ public class Workspace extends PagedView } } + public float getWallpaperOffsetForCenterPage() { + int pageScroll = getScrollForPage(getPageNearestToCenterOfScreen()); + return mWallpaperOffset.wallpaperOffsetForScroll(pageScroll); + } + public Rect estimateItemPosition(CellLayout cl, int hCell, int vCell, int hSpan, int vSpan) { Rect r = new Rect(); cl.cellToRect(hCell, vCell, hSpan, vSpan, r); diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 2fefa85ea3..32410a64b8 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -20,7 +20,6 @@ import android.app.Notification; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; -import android.support.annotation.Nullable; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; @@ -83,7 +82,7 @@ public class NotificationItemView { public void addGutter() { if (mGutter == null) { - mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter); + mGutter = mContainer.inflateAndAdd(R.layout.notification_gutter, mContainer); } } diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java new file mode 100644 index 0000000000..bd08aaa0eb --- /dev/null +++ b/src/com/android/launcher3/popup/ArrowPopup.java @@ -0,0 +1,470 @@ +/* + * Copyright (C) 2018 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 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.content.Context; +import android.content.res.Resources; +import android.graphics.CornerPathEffect; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.ShapeDrawable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewOutlineProvider; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AccelerateDecelerateInterpolator; + +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.anim.RevealOutlineAnimation; +import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; +import com.android.launcher3.dragndrop.DragLayer; +import com.android.launcher3.graphics.TriangleShape; +import com.android.launcher3.util.Themes; + +import java.util.ArrayList; +import java.util.Collections; + +import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; + +/** + * A container for shortcuts to deep links and notifications associated with an app. + */ +public abstract class ArrowPopup extends AbstractFloatingView { + + private final Rect mTempRect = new Rect(); + + protected final LayoutInflater mInflater; + private final float mOutlineRadius; + protected final Launcher mLauncher; + protected final boolean mIsRtl; + + private final int mArrayOffset; + private final View mArrow; + + protected boolean mIsLeftAligned; + protected boolean mIsAboveIcon; + private int mGravity; + + protected Animator mOpenCloseAnimator; + protected boolean mDeferContainerRemoval; + private final Rect mStartRect = new Rect(); + private final Rect mEndRect = new Rect(); + + public ArrowPopup(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + mInflater = LayoutInflater.from(context); + mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius); + mLauncher = Launcher.getLauncher(context); + mIsRtl = Utilities.isRtl(getResources()); + + setClipToOutline(true); + setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius); + } + }); + + // Initialize arrow view + final Resources resources = getResources(); + final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); + final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); + mArrow = new View(context); + mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight)); + mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); + } + + public ArrowPopup(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ArrowPopup(Context context) { + this(context, null, 0); + } + + @Override + protected void handleClose(boolean animate) { + if (animate) { + animateClose(); + } else { + closeComplete(); + } + } + + public T inflateAndAdd(int resId, ViewGroup container) { + View view = mInflater.inflate(resId, container, false); + container.addView(view); + return (T) view; + } + + /** + * Called when all view inflation and reordering in complete. + */ + protected void onInflationComplete(boolean isReversed) { } + + /** + * Shows the popup at the desired location, optionally reversing the children. + * @param viewsToFlip number of views from the top to to flip in case of reverse order + */ + protected void reorderAndShow(int viewsToFlip) { + setVisibility(View.INVISIBLE); + mIsOpen = true; + mLauncher.getDragLayer().addView(this); + orientAboutObject(); + + boolean reverseOrder = mIsAboveIcon; + if (reverseOrder) { + int count = getChildCount(); + ArrayList allViews = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + if (i == viewsToFlip) { + Collections.reverse(allViews); + } + allViews.add(getChildAt(i)); + } + Collections.reverse(allViews); + removeAllViews(); + for (int i = 0; i < count; i++) { + addView(allViews.get(i)); + } + + orientAboutObject(); + } + onInflationComplete(reverseOrder); + + // Add the arrow. + final Resources res = getResources(); + final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart() + ? R.dimen.popup_arrow_horizontal_center_start + : R.dimen.popup_arrow_horizontal_center_end); + final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; + mLauncher.getDragLayer().addView(mArrow); + DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); + if (mIsLeftAligned) { + mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth); + } else { + mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth); + } + + 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 showDefaultOptions the arrow. + mArrow.setVisibility(INVISIBLE); + } else { + ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( + arrowLp.width, arrowLp.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)); + mArrow.setBackground(arrowDrawable); + mArrow.setElevation(getElevation()); + } + + mArrow.setPivotX(arrowLp.width / 2); + mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height); + + animateOpen(); + } + + protected boolean isAlignedWithStart() { + return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; + } + + /** + * Provide the location of the target object relative to the dragLayer. + */ + protected abstract void getTargetObjectLocation(Rect outPos); + + /** + * 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 orientAboutObject() { + measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); + int width = getMeasuredWidth(); + int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset + + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); + int height = getMeasuredHeight() + extraVerticalSpace; + + getTargetObjectLocation(mTempRect); + DragLayer dragLayer = mLauncher.getDragLayer(); + Rect insets = dragLayer.getInsets(); + + // Align left (right in RTL) if there is room. + int leftAlignedX = mTempRect.left; + int rightAlignedX = mTempRect.right - width; + 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; + + // Offset x so that the arrow and shortcut icons are center-aligned with the original icon. + int iconWidth = mTempRect.width(); + 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 = mTempRect.height(); + int y = mTempRect.top - height; + mIsAboveIcon = y > dragLayer.getTop() + insets.top; + if (!mIsAboveIcon) { + y = mTempRect.top + iconHeight + extraVerticalSpace; + } + + // 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); + if (Gravity.isVertical(mGravity)) { + return; + } + + DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); + DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); + if (mIsAboveIcon) { + arrowLp.gravity = lp.gravity = Gravity.BOTTOM; + lp.bottomMargin = + mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top; + arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom; + } else { + arrowLp.gravity = lp.gravity = Gravity.TOP; + lp.topMargin = y + insets.top; + arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset; + } + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + + // enforce contained is within screen + DragLayer dragLayer = mLauncher.getDragLayer(); + if (getTranslationX() + l < 0 || getTranslationX() + r > 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); + mArrow.setVisibility(INVISIBLE); + } + if (Gravity.isVertical(mGravity)) { + setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2); + } + } + + private void animateOpen() { + setVisibility(View.VISIBLE); + + 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. + final ValueAnimator revealAnim = createOpenCloseOutlineProvider() + .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 = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1) + .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration)); + + openAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + sendCustomAccessibilityEvent( + ArrowPopup.this, + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, + getContext().getString(R.string.action_deep_shortcut)); + } + }); + + mOpenCloseAnimator = openAnim; + openAnim.playSequentially(revealAnim, arrowScale); + openAnim.start(); + } + + protected void animateClose() { + if (!mIsOpen) { + return; + } + mEndRect.setEmpty(); + if (getOutlineProvider() instanceof RevealOutlineAnimation) { + ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect); + } + if (mOpenCloseAnimator != null) { + mOpenCloseAnimator.cancel(); + } + mIsOpen = false; + + final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); + // Hide the arrow + closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0)); + closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0)); + + final Resources res = getResources(); + final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); + + // Rectangular reveal (reversed). + final ValueAnimator revealAnim = createOpenCloseOutlineProvider() + .createRevealAnimator(this, true); + revealAnim.setInterpolator(revealInterpolator); + closeAnim.play(revealAnim); + + Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0); + fadeOut.setInterpolator(revealInterpolator); + closeAnim.play(fadeOut); + + onCreateCloseAnimation(closeAnim); + closeAnim.setDuration((long) res.getInteger(R.integer.config_popupOpenCloseDuration)); + closeAnim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mOpenCloseAnimator = null; + if (mDeferContainerRemoval) { + setVisibility(INVISIBLE); + } else { + closeComplete(); + } + } + }); + mOpenCloseAnimator = closeAnim; + closeAnim.start(); + } + + /** + * Called when creating the close transition allowing subclass can add additional animations. + */ + protected void onCreateCloseAnimation(AnimatorSet anim) { } + + private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { + 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 arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0; + + mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY); + if (mEndRect.isEmpty()) { + mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + } + + return new RoundedRectRevealOutlineProvider + (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect); + } + + /** + * Closes the popup without animation. + */ + protected void closeComplete() { + if (mOpenCloseAnimator != null) { + mOpenCloseAnimator.cancel(); + mOpenCloseAnimator = null; + } + mIsOpen = false; + mDeferContainerRemoval = false; + mLauncher.getDragLayer().removeView(this); + mLauncher.getDragLayer().removeView(mArrow); + } +} diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 033fdf8889..422a4ecd26 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -16,36 +16,28 @@ package com.android.launcher3.popup; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; +import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO; +import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS; +import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; +import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; +import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; + import android.animation.AnimatorSet; import android.animation.LayoutTransition; -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.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.ViewGroup; -import android.view.ViewOutlineProvider; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.ImageView; import com.android.launcher3.AbstractFloatingView; @@ -56,20 +48,15 @@ import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; import com.android.launcher3.ItemInfoWithIcon; 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.RevealOutlineAnimation; -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.dragndrop.DragView; -import com.android.launcher3.graphics.TriangleShape; import com.android.launcher3.logging.LoggerUtils; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationItemView; @@ -79,85 +66,38 @@ import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider; import com.android.launcher3.touch.ItemLongClickListener; import com.android.launcher3.util.PackageUserKey; -import com.android.launcher3.util.Themes; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; -import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent; -import static com.android.launcher3.notification.NotificationMainView.NOTIFICATION_ITEM_INFO; -import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS; -import static com.android.launcher3.popup.PopupPopulator.MAX_SHORTCUTS_IF_NOTIFICATIONS; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType; -import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType; -import static com.android.launcher3.userevent.nano.LauncherLogProto.Target; - /** * 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 ArrowPopup implements DragSource, DragController.DragListener, View.OnLongClickListener, View.OnTouchListener { private final List mShortcuts = new ArrayList<>(); private final PointF mInterceptTouchDown = new PointF(); - private final Rect mTempRect = new Rect(); private final Point mIconLastTouchPos = new Point(); private final int mStartDragThreshold; - private final LayoutInflater mInflater; - private final float mOutlineRadius; - private final Launcher mLauncher; private final LauncherAccessibilityDelegate mAccessibilityDelegate; - private final boolean mIsRtl; - - private final int mArrayOffset; - private final View mArrow; private BubbleTextView mOriginalIcon; private NotificationItemView mNotificationItemView; + private int mNumNotifications; private ViewGroup mSystemShortcutContainer; - private boolean mIsLeftAligned; - protected boolean mIsAboveIcon; - private int mNumNotifications; - private int mGravity; - - protected Animator mOpenCloseAnimator; - protected boolean mDeferContainerRemoval; - private final Rect mStartRect = new Rect(); - private final Rect mEndRect = new Rect(); - public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mStartDragThreshold = getResources().getDimensionPixelSize( R.dimen.deep_shortcuts_start_drag_threshold); - mInflater = LayoutInflater.from(context); - mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius); - mLauncher = Launcher.getLauncher(context); mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher); - mIsRtl = Utilities.isRtl(getResources()); - - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius); - } - }); - - // Initialize arrow view - final Resources resources = getResources(); - final int arrowWidth = resources.getDimensionPixelSize(R.dimen.popup_arrow_width); - final int arrowHeight = resources.getDimensionPixelSize(R.dimen.popup_arrow_height); - mArrow = new View(context); - mArrow.setLayoutParams(new DragLayer.LayoutParams(arrowWidth, arrowHeight)); - mArrayOffset = resources.getDimensionPixelSize(R.dimen.popup_arrow_vertical_offset); } public PopupContainerWithArrow(Context context, AttributeSet attrs) { @@ -222,21 +162,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra return false; } - @Override - protected void handleClose(boolean animate) { - if (animate) { - animateClose(); - } else { - closeComplete(); - } - } - - public T inflateAndAdd(int resId) { - View view = mInflater.inflate(resId, this, false); - addView(view); - return (T) view; - } - /** * Shows the notifications and deep shortcuts associated with {@param icon}. * @return the container if shown or null. @@ -267,13 +192,30 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra return container; } + @Override + protected void onInflationComplete(boolean isReversed) { + if (isReversed && mNotificationItemView != null) { + mNotificationItemView.inverseGutterMargin(); + } + + // Update dividers + int count = getChildCount(); + DeepShortcutView lastView = null; + for (int i = 0; i < count; i++) { + View view = getChildAt(i); + if (view.getVisibility() == VISIBLE && view instanceof DeepShortcutView) { + if (lastView != null) { + lastView.setDividerVisibility(VISIBLE); + } + lastView = (DeepShortcutView) view; + lastView.setDividerVisibility(INVISIBLE); + } + } + } + private void populateAndShow(final BubbleTextView originalIcon, final List shortcutIds, final List notificationKeys, List systemShortcuts) { mNumNotifications = notificationKeys.size(); - - setVisibility(View.INVISIBLE); - mLauncher.getDragLayer().addView(this); - mOriginalIcon = originalIcon; // Add views @@ -295,17 +237,15 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } for (int i = shortcutIds.size(); i > 0; i--) { - mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut)); + mShortcuts.add(inflateAndAdd(R.layout.deep_shortcut, this)); } updateHiddenShortcuts(); if (!systemShortcuts.isEmpty()) { - mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons); + mSystemShortcutContainer = inflateAndAdd(R.layout.system_shortcut_icons, this); for (SystemShortcut shortcut : systemShortcuts) { - View view = mInflater.inflate(R.layout.system_shortcut_icon_only, - mSystemShortcutContainer, false); - mSystemShortcutContainer.addView(view); - initializeSystemShortcut(view, shortcut); + initializeSystemShortcut( + R.layout.system_shortcut_icon_only, mSystemShortcutContainer, shortcut); } } } else if (!systemShortcuts.isEmpty()) { @@ -314,68 +254,11 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } for (SystemShortcut shortcut : systemShortcuts) { - initializeSystemShortcut(inflateAndAdd(R.layout.system_shortcut), shortcut); + initializeSystemShortcut(R.layout.system_shortcut, this, shortcut); } } - orientAboutIcon(); - boolean reverseOrder = mIsAboveIcon; - if (reverseOrder) { - int count = getChildCount(); - ArrayList allViews = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - if (i == viewsToFlip) { - Collections.reverse(allViews); - } - allViews.add(getChildAt(i)); - } - Collections.reverse(allViews); - removeAllViews(); - for (int i = 0; i < count; i++) { - addView(allViews.get(i)); - } - if (mNotificationItemView != null) { - mNotificationItemView.inverseGutterMargin(); - } - - orientAboutIcon(); - } - updateDividers(); - - // Add the arrow. - final Resources res = getResources(); - final int arrowCenterOffset = res.getDimensionPixelSize(isAlignedWithStart() - ? R.dimen.popup_arrow_horizontal_center_start - : R.dimen.popup_arrow_horizontal_center_end); - final int halfArrowWidth = res.getDimensionPixelSize(R.dimen.popup_arrow_width) / 2; - mLauncher.getDragLayer().addView(mArrow); - DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); - if (mIsLeftAligned) { - mArrow.setX(getX() + arrowCenterOffset - halfArrowWidth); - } else { - mArrow.setX(getX() + getMeasuredWidth() - arrowCenterOffset - halfArrowWidth); - } - - 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. - mArrow.setVisibility(INVISIBLE); - } else { - ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create( - arrowLp.width, arrowLp.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)); - mArrow.setBackground(arrowDrawable); - mArrow.setElevation(getElevation()); - } - - mArrow.setPivotX(arrowLp.width / 2); - mArrow.setPivotY(mIsAboveIcon ? 0 : arrowLp.height); - - animateOpen(); + reorderAndShow(viewsToFlip); ItemInfo originalItemInfo = (ItemInfo) originalIcon.getTag(); int numShortcuts = mShortcuts.size() + systemShortcuts.size(); @@ -401,189 +284,15 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra this, shortcutIds, mShortcuts, notificationKeys)); } - protected boolean isAlignedWithStart() { - return mIsLeftAligned && !mIsRtl || !mIsLeftAligned && mIsRtl; - } - - /** - * 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() { - measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); - int width = getMeasuredWidth(); - int extraVerticalSpace = mArrow.getLayoutParams().height + mArrayOffset - + getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding); - int height = getMeasuredHeight() + extraVerticalSpace; - - 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; - - // 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 + extraVerticalSpace; - } - - // 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); - if (Gravity.isVertical(mGravity)) { - return; - } - - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - DragLayer.LayoutParams arrowLp = (DragLayer.LayoutParams) mArrow.getLayoutParams(); - if (mIsAboveIcon) { - arrowLp.gravity = lp.gravity = Gravity.BOTTOM; - lp.bottomMargin = - mLauncher.getDragLayer().getHeight() - y - getMeasuredHeight() - insets.top; - arrowLp.bottomMargin = lp.bottomMargin - arrowLp.height - mArrayOffset - insets.bottom; - } else { - arrowLp.gravity = lp.gravity = Gravity.TOP; - lp.topMargin = y + insets.top; - arrowLp.topMargin = lp.topMargin - insets.top - arrowLp.height - mArrayOffset; - } - } - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - - // enforce contained is within screen - DragLayer dragLayer = mLauncher.getDragLayer(); - if (getTranslationX() + l < 0 || getTranslationX() + r > 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); - mArrow.setVisibility(INVISIBLE); - } - if (Gravity.isVertical(mGravity)) { - setY(dragLayer.getHeight() / 2 - getMeasuredHeight() / 2); - } - } - - 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. - final ValueAnimator revealAnim = createOpenCloseOutlineProvider() - .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 = ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 1) - .setDuration(res.getInteger(R.integer.config_popupArrowOpenDuration)); - - openAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - sendCustomAccessibilityEvent( - PopupContainerWithArrow.this, - AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED, - getContext().getString(R.string.action_deep_shortcut)); - } - }); - - mOpenCloseAnimator = openAnim; - openAnim.playSequentially(revealAnim, arrowScale); - openAnim.start(); + protected void getTargetObjectLocation(Rect outPos) { + mLauncher.getDragLayer().getDescendantRectRelativeToSelf(mOriginalIcon, outPos); + outPos.top += mOriginalIcon.getPaddingTop(); + outPos.left += mOriginalIcon.getPaddingLeft(); + outPos.right -= mOriginalIcon.getPaddingRight(); + outPos.bottom = outPos.top + (mOriginalIcon.getIcon() != null + ? mOriginalIcon.getIcon().getBounds().height() + : mOriginalIcon.getHeight()); } public void applyNotificationInfos(List notificationInfos) { @@ -642,10 +351,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra if (onClickListener != null && widgetsView == null) { // We didn't have any widgets cached but now there are some, so enable the shortcut. if (mSystemShortcutContainer != this) { - View view = mInflater.inflate(R.layout.system_shortcut_icon_only, - mSystemShortcutContainer, false); - mSystemShortcutContainer.addView(view); - initializeSystemShortcut(view, widgetInfo); + initializeSystemShortcut( + R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo); } else { // If using the expanded system shortcut (as opposed to just the icon), we need to // reopen the container to ensure measurements etc. all work out. While this could @@ -665,7 +372,8 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } } - private void initializeSystemShortcut(View view, SystemShortcut info) { + private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) { + View view = inflateAndAdd(resId, container); if (view instanceof DeepShortcutView) { // Expanded system shortcut, with both icon and text shown on white background. final DeepShortcutView shortcutView = (DeepShortcutView) view; @@ -682,12 +390,6 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra (ItemInfo) mOriginalIcon.getTag())); } - protected int getIconHeightForPopupPlacement() { - return mOriginalIcon.getIcon() != null - ? mOriginalIcon.getIcon().getBounds().height() - : mOriginalIcon.getHeight(); - } - /** * Determines when the deferred drag should be started. * @@ -807,91 +509,11 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra targetParent.containerType = ContainerType.DEEPSHORTCUTS; } - protected void animateClose() { - if (!mIsOpen) { - return; - } - mEndRect.setEmpty(); - if (getOutlineProvider() instanceof RevealOutlineAnimation) { - ((RevealOutlineAnimation) getOutlineProvider()).getOutline(mEndRect); - } - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - } - mIsOpen = false; - - final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); - // Hide the arrow - closeAnim.play(ObjectAnimator.ofFloat(mArrow, LauncherAnimUtils.SCALE_PROPERTY, 0)); - closeAnim.play(ObjectAnimator.ofFloat(mArrow, ALPHA, 0)); - + @Override + protected void onCreateCloseAnimation(AnimatorSet anim) { // Animate original icon's text back in. - closeAnim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); + anim.play(mOriginalIcon.createTextAlphaAnimator(true /* fadeIn */)); mOriginalIcon.forceHideBadge(false); - - final Resources res = getResources(); - final TimeInterpolator revealInterpolator = new AccelerateDecelerateInterpolator(); - - // Rectangular reveal (reversed). - final ValueAnimator revealAnim = createOpenCloseOutlineProvider() - .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)); - - closeAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - if (mDeferContainerRemoval) { - setVisibility(INVISIBLE); - } else { - closeComplete(); - } - } - }); - mOpenCloseAnimator = closeAnim; - closeAnim.start(); - } - - private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { - 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 arrowCenterY = mIsAboveIcon ? getMeasuredHeight() : 0; - - mStartRect.set(arrowCenterX, arrowCenterY, arrowCenterX, arrowCenterY); - if (mEndRect.isEmpty()) { - mEndRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); - } - - return new RoundedRectRevealOutlineProvider - (mOutlineRadius, mOutlineRadius, mStartRect, mEndRect); - } - - /** - * Closes the popup without animation. - */ - private void closeComplete() { - mOriginalIcon.setTextVisibility(mOriginalIcon.shouldTextBeVisible()); - mOriginalIcon.forceHideBadge(false); - - mLauncher.getDragController().removeDragListener(this); - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - mOpenCloseAnimator = null; - } - mIsOpen = false; - mDeferContainerRemoval = false; - mLauncher.getDragLayer().removeView(this); - mLauncher.getDragLayer().removeView(mArrow); } @Override diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java index 2f9cf3aab6..23f55aa17e 100644 --- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java +++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java @@ -144,7 +144,7 @@ public class WorkspaceTouchListener implements OnTouchListener, Runnable { mLauncher.getUserEventDispatcher().logActionOnContainer(Action.Touch.LONGPRESS, Action.Direction.NONE, ContainerType.WORKSPACE, mWorkspace.getCurrentPage()); - OptionsPopupView.show(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y); + OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y); } } } diff --git a/src/com/android/launcher3/views/LauncherDragIndicator.java b/src/com/android/launcher3/views/LauncherDragIndicator.java index f15129cf48..986e4bee31 100644 --- a/src/com/android/launcher3/views/LauncherDragIndicator.java +++ b/src/com/android/launcher3/views/LauncherDragIndicator.java @@ -108,15 +108,12 @@ public class LauncherDragIndicator extends ImageView implements Insettable, OnCl @Override public boolean performAccessibilityAction(int action, Bundle arguments) { - Launcher launcher = Launcher.getLauncher(getContext()); if (action == WALLPAPERS) { - launcher.onClickWallpaperPicker(this); - return true; + return OptionsPopupView.startWallpaperPicker(this); } else if (action == WIDGETS) { - return OptionsPopupView.onWidgetsClicked(launcher); + return OptionsPopupView.onWidgetsClicked(this); } else if (action == SETTINGS) { - OptionsPopupView.startSettings(launcher); - return true; + return OptionsPopupView.startSettings(this); } return super.performAccessibilityAction(action, arguments); } diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java index 709a7e5c8b..56b92c7c62 100644 --- a/src/com/android/launcher3/views/OptionsPopupView.java +++ b/src/com/android/launcher3/views/OptionsPopupView.java @@ -15,52 +15,42 @@ */ package com.android.launcher3.views; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; +import static com.android.launcher3.BaseDraggingActivity.INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION; +import static com.android.launcher3.Utilities.EXTRA_WALLPAPER_OFFSET; + import android.content.Context; import android.content.Intent; -import android.graphics.Outline; -import android.graphics.PointF; import android.graphics.Rect; +import android.graphics.RectF; +import android.text.TextUtils; +import android.util.ArrayMap; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLongClickListener; -import android.view.ViewGroup; -import android.view.ViewOutlineProvider; import android.widget.Toast; -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.anim.Interpolators; -import com.android.launcher3.anim.RevealOutlineAnimation; -import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; -import com.android.launcher3.dragndrop.DragLayer; -import com.android.launcher3.graphics.ColorScrim; +import com.android.launcher3.popup.ArrowPopup; +import com.android.launcher3.shortcuts.DeepShortcutView; import com.android.launcher3.userevent.nano.LauncherLogProto.Action; import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType; import com.android.launcher3.widget.WidgetsFullSheet; +import java.util.ArrayList; +import java.util.List; + /** * Popup shown on long pressing an empty space in launcher */ -public class OptionsPopupView extends AbstractFloatingView +public class OptionsPopupView extends ArrowPopup implements OnClickListener, OnLongClickListener { - private final float mOutlineRadius; - private final Launcher mLauncher; - private final PointF mTouchPoint = new PointF(); - - private final ColorScrim mScrim; - - protected Animator mOpenCloseAnimator; + private final ArrayMap mItemMap = new ArrayMap<>(); + private RectF mTargetRect; public OptionsPopupView(Context context, AttributeSet attrs) { this(context, attrs, 0); @@ -68,31 +58,6 @@ public class OptionsPopupView extends AbstractFloatingView public OptionsPopupView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - - mOutlineRadius = getResources().getDimension(R.dimen.bg_round_rect_radius); - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mOutlineRadius); - } - }); - - mLauncher = Launcher.getLauncher(context); - mScrim = ColorScrim.createExtractedColorScrim(this); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - attachListeners(findViewById(R.id.wallpaper_button)); - attachListeners(findViewById(R.id.widget_button)); - attachListeners(findViewById(R.id.settings_button)); - } - - private void attachListeners(View view) { - view.setOnClickListener(this); - view.setOnLongClickListener(this); } @Override @@ -106,20 +71,14 @@ public class OptionsPopupView extends AbstractFloatingView } private boolean handleViewClick(View view, int action) { - if (view.getId() == R.id.wallpaper_button) { - mLauncher.onClickWallpaperPicker(view); - logTap(action, ControlType.WALLPAPER_BUTTON); - close(true); - return true; - } else if (view.getId() == R.id.widget_button) { - logTap(action, ControlType.WIDGETS_BUTTON); - if (onWidgetsClicked(mLauncher)) { - close(true); - return true; - } - } else if (view.getId() == R.id.settings_button) { - startSettings(mLauncher); - logTap(action, ControlType.SETTINGS_BUTTON); + OptionItem item = mItemMap.get(view); + if (item == null) { + return false; + } + if (item.mControlTypeForLog > 0) { + logTap(action, item.mControlTypeForLog); + } + if (item.mClickListener.onLongClick(view)) { close(true); return true; } @@ -142,63 +101,6 @@ public class OptionsPopupView extends AbstractFloatingView return true; } - @Override - protected void handleClose(boolean animate) { - if (animate) { - animateClose(); - } else { - closeComplete(); - } - } - - protected void animateClose() { - if (!mIsOpen) { - return; - } - mIsOpen = false; - - final AnimatorSet closeAnim = LauncherAnimUtils.createAnimatorSet(); - closeAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration)); - - // Rectangular reveal (reversed). - final ValueAnimator revealAnim = createOpenCloseOutlineProvider() - .createRevealAnimator(this, true); - closeAnim.play(revealAnim); - - Animator fadeOut = ObjectAnimator.ofFloat(this, ALPHA, 0); - fadeOut.setInterpolator(Interpolators.DEACCEL); - closeAnim.play(fadeOut); - - Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 0); - gradientAlpha.setInterpolator(Interpolators.DEACCEL); - closeAnim.play(gradientAlpha); - - closeAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - closeComplete(); - } - }); - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - } - mOpenCloseAnimator = closeAnim; - closeAnim.start(); - } - - /** - * Closes the popup without animation. - */ - private void closeComplete() { - if (mOpenCloseAnimator != null) { - mOpenCloseAnimator.cancel(); - mOpenCloseAnimator = null; - } - mIsOpen = false; - mLauncher.getDragLayer().removeView(this); - } - @Override public void logActionCommand(int command) { // TODO: @@ -209,90 +111,49 @@ public class OptionsPopupView extends AbstractFloatingView return (type & TYPE_OPTIONS_POPUP) != 0; } - private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() { - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) getLayoutParams(); - Rect startRect = new Rect(); - startRect.offset((int) (mTouchPoint.x - lp.x), (int) (mTouchPoint.y - lp.y)); - - Rect endRect = new Rect(0, 0, lp.width, lp.height); - if (getOutlineProvider() instanceof RevealOutlineAnimation) { - ((RevealOutlineAnimation) getOutlineProvider()).getOutline(endRect); - } - - return new RoundedRectRevealOutlineProvider - (mOutlineRadius, mOutlineRadius, startRect, endRect); + @Override + protected void getTargetObjectLocation(Rect outPos) { + mTargetRect.roundOut(outPos); } - private void animateOpen() { - mIsOpen = true; - final AnimatorSet openAnim = LauncherAnimUtils.createAnimatorSet(); - openAnim.setDuration(getResources().getInteger(R.integer.config_popupOpenCloseDuration)); + public static void show(Launcher launcher, RectF targetRect, List items) { + OptionsPopupView popup = (OptionsPopupView) launcher.getLayoutInflater() + .inflate(R.layout.longpress_options_menu, launcher.getDragLayer(), false); + popup.mTargetRect = targetRect; - final ValueAnimator revealAnim = createOpenCloseOutlineProvider() - .createRevealAnimator(this, false); - openAnim.play(revealAnim); - - Animator gradientAlpha = ObjectAnimator.ofFloat(mScrim, ColorScrim.PROGRESS, 1); - gradientAlpha.setInterpolator(Interpolators.ACCEL); - openAnim.play(gradientAlpha); - - mOpenCloseAnimator = openAnim; - - openAnim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mOpenCloseAnimator = null; - } - }); - openAnim.start(); + for (OptionItem item : items) { + DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup); + view.getIconView().setBackgroundResource(item.mIconRes); + view.getBubbleText().setText(item.mLabelRes); + view.setDividerVisibility(View.INVISIBLE); + view.setOnClickListener(popup); + view.setOnLongClickListener(popup); + popup.mItemMap.put(view, item); + } + popup.reorderAndShow(popup.getChildCount()); } - public static void show(Launcher launcher, float x, float y) { - DragLayer dl = launcher.getDragLayer(); - OptionsPopupView view = (OptionsPopupView) launcher.getLayoutInflater() - .inflate(R.layout.longpress_options_menu, dl, false); - DragLayer.LayoutParams lp = (DragLayer.LayoutParams) view.getLayoutParams(); - - int maxWidth = dl.getWidth(); - int maxHeight = dl.getHeight(); - if (x <= 0 || y <= 0 || x >= maxWidth || y >= maxHeight) { - x = maxWidth / 2; - y = maxHeight / 2; + public static void showDefaultOptions(Launcher launcher, float x, float y) { + float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2; + if (x < 0 || y < 0) { + x = launcher.getDragLayer().getWidth() / 2; + y = launcher.getDragLayer().getHeight() / 2; } - view.mTouchPoint.set(x, y); + RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize); - int height = lp.height; + ArrayList options = new ArrayList<>(); + options.add(new OptionItem(R.string.wallpaper_button_text, R.drawable.ic_wallpaper, + ControlType.WALLPAPER_BUTTON, OptionsPopupView::startWallpaperPicker)); + options.add(new OptionItem(R.string.widget_button_text, R.drawable.ic_widget, + ControlType.WIDGETS_BUTTON, OptionsPopupView::onWidgetsClicked)); + options.add(new OptionItem(R.string.settings_button_text, R.drawable.ic_setting, + ControlType.SETTINGS_BUTTON, OptionsPopupView::startSettings)); - // Find a good width; - int childCount = view.getChildCount(); - int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST); - int widthSpec = MeasureSpec.makeMeasureSpec(maxWidth / childCount, MeasureSpec.AT_MOST); - int maxChildWidth = 0; - - for (int i = 0; i < childCount; i ++) { - View child = ((ViewGroup) view.getChildAt(i)).getChildAt(0); - child.measure(widthSpec, heightSpec); - maxChildWidth = Math.max(maxChildWidth, child.getMeasuredWidth()); - } - Rect insets = dl.getInsets(); - int margin = (int) (2 * view.getElevation()); - - int width = Math.min(maxWidth - insets.left - insets.right - 2 * margin, - maxChildWidth * childCount); - lp.width = width; - - // Position is towards the finger - lp.customPosition = true; - lp.x = Utilities.boundToRange((int) (x - width / 2), insets.left + margin, - maxWidth - insets.right - width - margin); - lp.y = Utilities.boundToRange((int) (y - height / 2), insets.top + margin, - maxHeight - insets.bottom - height - margin); - - view.animateOpen(); - launcher.getDragLayer().addView(view); + show(launcher, target, options); } - public static boolean onWidgetsClicked(Launcher launcher) { + public static boolean onWidgetsClicked(View view) { + Launcher launcher = Launcher.getLauncher(view.getContext()); if (launcher.getPackageManager().isSafeMode()) { Toast.makeText(launcher, R.string.safemode_widget_error, Toast.LENGTH_SHORT).show(); return false; @@ -302,9 +163,51 @@ public class OptionsPopupView extends AbstractFloatingView } } - public static void startSettings(Launcher launcher) { + public static boolean startSettings(View view) { + Launcher launcher = Launcher.getLauncher(view.getContext()); launcher.startActivity(new Intent(Intent.ACTION_APPLICATION_PREFERENCES) .setPackage(launcher.getPackageName()) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + return true; + } + + /** + * Event handler for the wallpaper picker button that appears after a long press + * on the home screen. + */ + public static boolean startWallpaperPicker(View v) { + Launcher launcher = Launcher.getLauncher(v.getContext()); + if (!Utilities.isWallpaperAllowed(launcher)) { + Toast.makeText(launcher, R.string.msg_disabled_by_admin, Toast.LENGTH_SHORT).show(); + return false; + } + Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER) + .putExtra(EXTRA_WALLPAPER_OFFSET, + launcher.getWorkspace().getWallpaperOffsetForCenterPage()); + + String pickerPackage = launcher.getString(R.string.wallpaper_picker_package); + if (!TextUtils.isEmpty(pickerPackage)) { + intent.setPackage(pickerPackage); + } else { + // If there is no target package, use the default intent chooser animation + intent.putExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION, true); + } + return launcher.startActivitySafely(v, intent, null); + } + + public static class OptionItem { + + private final int mLabelRes; + private final int mIconRes; + private final int mControlTypeForLog; + private final OnLongClickListener mClickListener; + + public OptionItem(int labelRes, int iconRes, int controlTypeForLog, + OnLongClickListener clickListener) { + mLabelRes = labelRes; + mIconRes = iconRes; + mControlTypeForLog = controlTypeForLog; + mClickListener = clickListener; + } } }