Updating the UI of the options popup to make it look similar to icon popup
Bug: 77327164
Change-Id: I3580df8bf8a43cb44123f203ffed9a85fa33aea7
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 4c11fe6..bde9ad3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -47,7 +47,7 @@
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 e2f7488..ccc774a 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1634,30 +1634,6 @@
}
}
- /**
- * 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 @@
// 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 34ae8ea..a208c7a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -349,6 +349,11 @@
}
}
+ 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 2fefa85..32410a6 100644
--- a/src/com/android/launcher3/notification/NotificationItemView.java
+++ b/src/com/android/launcher3/notification/NotificationItemView.java
@@ -20,7 +20,6 @@
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 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 0000000..bd08aaa
--- /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 extends View> 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<View> 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 033fdf8..422a4ec 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.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.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<DeepShortcutView> 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 @@
return false;
}
- @Override
- protected void handleClose(boolean animate) {
- if (animate) {
- animateClose();
- } else {
- closeComplete();
- }
- }
-
- public <T extends View> 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 @@
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<String> shortcutIds,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
-
- setVisibility(View.INVISIBLE);
- mLauncher.getDragLayer().addView(this);
-
mOriginalIcon = originalIcon;
// Add views
@@ -295,17 +237,15 @@
}
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 @@
}
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<View> 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 @@
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<NotificationInfo> notificationInfos) {
@@ -642,10 +351,8 @@
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 @@
}
}
- 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 @@
(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 @@
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 2f9cf3a..23f55aa 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -144,7 +144,7 @@
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 f15129c..986e4be 100644
--- a/src/com/android/launcher3/views/LauncherDragIndicator.java
+++ b/src/com/android/launcher3/views/LauncherDragIndicator.java
@@ -108,15 +108,12 @@
@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 709a7e5..56b92c7 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<View, OptionItem> mItemMap = new ArrayMap<>();
+ private RectF mTargetRect;
public OptionsPopupView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
@@ -68,31 +58,6 @@
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 @@
}
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;
}
@@ -143,63 +102,6 @@
}
@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 @@
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<OptionItem> 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<OptionItem> 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 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;
+ }
}
}