Merge "open grid task menu to the right of icon" into sc-v2-dev am: bd1aec6721

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/16248618

Change-Id: I5552cc4a7222a22191553f1b88ad23fc3ef3f25c
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index e48c9e8..4e6b7b9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -28,6 +28,7 @@
     <dimen name="task_menu_item_corner_radius">4dp</dimen>
     <dimen name="task_menu_spacing">2dp</dimen>
     <dimen name="task_menu_width_grid">234dp</dimen>
+    <dimen name="task_menu_horizontal_padding">8dp</dimen>
     <dimen name="overview_proactive_row_height">48dp</dimen>
     <dimen name="overview_proactive_row_bottom_margin">16dp</dimen>
 
diff --git a/quickstep/src/com/android/quickstep/KtR.java b/quickstep/src/com/android/quickstep/KtR.java
index 57dad08..a768ef5 100644
--- a/quickstep/src/com/android/quickstep/KtR.java
+++ b/quickstep/src/com/android/quickstep/KtR.java
@@ -30,6 +30,7 @@
 
     public static final class dimen {
         public static int task_menu_spacing = R.dimen.task_menu_spacing;
+        public static int task_menu_horizontal_padding = R.dimen.task_menu_horizontal_padding;
     }
 
     public static final class layout {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
index 5059f8b..179fd68 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuViewWithArrow.kt
@@ -23,14 +23,18 @@
 import android.graphics.drawable.ShapeDrawable
 import android.graphics.drawable.shapes.RectShape
 import android.util.AttributeSet
+import android.view.Gravity
 import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import android.widget.LinearLayout
 import com.android.launcher3.BaseDraggingActivity
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.InsettableFrameLayout
 import com.android.launcher3.R
 import com.android.launcher3.popup.ArrowPopup
+import com.android.launcher3.popup.RoundedArrowDrawable
 import com.android.launcher3.popup.SystemShortcut
 import com.android.launcher3.util.Themes
 import com.android.quickstep.KtR
@@ -43,9 +47,13 @@
 
         fun showForTask(taskContainer: TaskIdAttributeContainer): Boolean {
             val activity = BaseDraggingActivity
-                    .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
+                .fromContext<BaseDraggingActivity>(taskContainer.taskView.context)
             val taskMenuViewWithArrow = activity.layoutInflater
-                    .inflate(KtR.layout.task_menu_with_arrow, activity.dragLayer, false) as TaskMenuViewWithArrow<*>
+                .inflate(
+                    KtR.layout.task_menu_with_arrow,
+                    activity.dragLayer,
+                    false
+                ) as TaskMenuViewWithArrow<*>
 
             return taskMenuViewWithArrow.populateAndShowForTask(taskContainer)
         }
@@ -53,10 +61,21 @@
 
     constructor(context: Context) : super(context)
     constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
-    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
+    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
+        context,
+        attrs,
+        defStyleAttr
+    )
 
     init {
         clipToOutline = true
+
+        shouldScaleArrow = true
+        // This synchronizes the arrow and menu to open at the same time
+        OPEN_CHILD_FADE_START_DELAY = OPEN_FADE_START_DELAY
+        OPEN_CHILD_FADE_DURATION = OPEN_FADE_DURATION
+        CLOSE_FADE_START_DELAY = CLOSE_CHILD_FADE_START_DELAY
+        CLOSE_FADE_DURATION = CLOSE_CHILD_FADE_DURATION
     }
 
     private val menuWidth = context.resources.getDimensionPixelSize(R.dimen.task_menu_width_grid)
@@ -65,6 +84,13 @@
     private lateinit var optionLayout: LinearLayout
     private lateinit var taskContainer: TaskIdAttributeContainer
 
+    private var optionMeasuredHeight = 0
+    private val arrowHorizontalPadding: Int
+        get() = if (taskView.isFocusedTask)
+            resources.getDimensionPixelSize(KtR.dimen.task_menu_horizontal_padding)
+        else
+            0
+
     override fun isOfType(type: Int): Boolean = type and TYPE_TASK_MENU != 0
 
     override fun getTargetObjectLocation(outPos: Rect?) {
@@ -147,7 +173,10 @@
     }
 
     override fun assignMarginsAndBackgrounds(viewGroup: ViewGroup) {
-        assignMarginsAndBackgrounds(this, Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface))
+        assignMarginsAndBackgrounds(
+            this,
+            Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)
+        )
     }
 
     override fun onCreateOpenAnimation(anim: AnimatorSet) {
@@ -164,4 +193,90 @@
             ObjectAnimator.ofFloat(taskContainer.thumbnailView, TaskThumbnailView.DIM_ALPHA, 0f)
         )
     }
+
+    /**
+     * Orients this container to the left or right of the given icon, aligning with the first option
+     * or second.
+     *
+     * These are the preferred orientations, in order (RTL prefers right-aligned over left):
+     * - Right and first option aligned
+     * - Right and second option aligned
+     * - Left and first option aligned
+     * - Left and second option aligned
+     *
+     * So we always align right if there is enough horizontal space
+     */
+    override fun orientAboutObject() {
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+        // Needed for offsets later
+        optionMeasuredHeight = optionLayout.getChildAt(0).measuredHeight
+        val extraHorizontalSpace = (mArrowHeight + mArrowOffsetVertical + arrowHorizontalPadding)
+
+        val widthWithArrow = measuredWidth + paddingLeft + paddingRight + extraHorizontalSpace
+        getTargetObjectLocation(mTempRect)
+        val dragLayer: InsettableFrameLayout = popupContainer
+        val insets = dragLayer.insets
+
+        // Put to the right of the icon if there is space, which means left aligned with the menu
+        val rightAlignedMenuStartX = mTempRect.left - widthWithArrow
+        val leftAlignedMenuStartX = mTempRect.right + extraHorizontalSpace
+        mIsLeftAligned = if (mIsRtl) {
+            rightAlignedMenuStartX + insets.left < 0
+        } else {
+            leftAlignedMenuStartX + (widthWithArrow - extraHorizontalSpace) + insets.left <
+                    dragLayer.width - insets.right
+        }
+
+        var menuStartX = if (mIsLeftAligned) leftAlignedMenuStartX else rightAlignedMenuStartX
+
+        // Offset y so that the arrow and first row are center-aligned with the original icon.
+        val iconHeight = mTempRect.height()
+        val optionHeight = optionMeasuredHeight
+        val yOffset = (optionHeight - iconHeight) / 2
+        var menuStartY = mTempRect.top - yOffset
+
+        // Insets are added later, so subtract them now.
+        menuStartX -= insets.left
+        menuStartY -= insets.top
+
+        setX(menuStartX.toFloat())
+        setY(menuStartY.toFloat())
+
+        val lp = layoutParams as FrameLayout.LayoutParams
+        val arrowLp = mArrow.layoutParams as FrameLayout.LayoutParams
+        lp.gravity = Gravity.TOP
+        arrowLp.gravity = lp.gravity
+    }
+
+    override fun addArrow() {
+        popupContainer.addView(mArrow)
+        mArrow.x = getArrowX()
+        mArrow.y = y + (optionMeasuredHeight / 2) - (mArrowHeight / 2)
+
+        updateArrowColor()
+
+        // This is inverted (x = height, y = width) because the arrow is rotated
+        mArrow.pivotX = if (mIsLeftAligned) 0f else mArrowHeight.toFloat()
+        mArrow.pivotY = 0f
+    }
+
+    private fun getArrowX(): Float {
+        return if (mIsLeftAligned)
+            x - mArrowHeight
+        else
+            x + measuredWidth + mArrowOffsetVertical
+    }
+
+    override fun updateArrowColor() {
+        mArrow.background = RoundedArrowDrawable(
+            mArrowWidth.toFloat(),
+            mArrowHeight.toFloat(),
+            mArrowPointRadius.toFloat(),
+            mIsLeftAligned,
+            mArrowColor
+        )
+        elevation = mElevation
+        mArrow.elevation = mElevation
+    }
+
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 5a1e4bf..b1a4109 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -77,44 +77,45 @@
         extends AbstractFloatingView {
 
     // Duration values (ms) for popup open and close animations.
-    private static final int OPEN_DURATION = 276;
-    private static final int OPEN_FADE_START_DELAY = 0;
-    private static final int OPEN_FADE_DURATION = 38;
-    private static final int OPEN_CHILD_FADE_START_DELAY = 38;
-    private static final int OPEN_CHILD_FADE_DURATION = 76;
+    protected int OPEN_DURATION = 276;
+    protected int OPEN_FADE_START_DELAY = 0;
+    protected int OPEN_FADE_DURATION = 38;
+    protected int OPEN_CHILD_FADE_START_DELAY = 38;
+    protected int OPEN_CHILD_FADE_DURATION = 76;
 
-    private static final int CLOSE_DURATION = 200;
-    private static final int CLOSE_FADE_START_DELAY = 140;
-    private static final int CLOSE_FADE_DURATION = 50;
-    private static final int CLOSE_CHILD_FADE_START_DELAY = 0;
-    private static final int CLOSE_CHILD_FADE_DURATION = 140;
+    protected int CLOSE_DURATION = 200;
+    protected int CLOSE_FADE_START_DELAY = 140;
+    protected int CLOSE_FADE_DURATION = 50;
+    protected int CLOSE_CHILD_FADE_START_DELAY = 0;
+    protected int CLOSE_CHILD_FADE_DURATION = 140;
 
     // Index used to get background color when using local wallpaper color extraction,
     private static final int DARK_COLOR_EXTRACTION_INDEX = android.R.color.system_neutral2_800;
     private static final int LIGHT_COLOR_EXTRACTION_INDEX = android.R.color.system_accent2_50;
 
-    private final Rect mTempRect = new Rect();
+    protected final Rect mTempRect = new Rect();
 
     protected final LayoutInflater mInflater;
-    private final float mOutlineRadius;
+    protected final float mOutlineRadius;
     protected final T mActivityContext;
     protected final boolean mIsRtl;
 
-    private final int mArrowOffsetVertical;
-    private final int mArrowOffsetHorizontal;
-    private final int mArrowWidth;
-    private final int mArrowHeight;
-    private final int mArrowPointRadius;
-    private final View mArrow;
+    protected final int mArrowOffsetVertical;
+    protected final int mArrowOffsetHorizontal;
+    protected final int mArrowWidth;
+    protected final int mArrowHeight;
+    protected final int mArrowPointRadius;
+    protected final View mArrow;
 
     private final int mMargin;
 
     protected boolean mIsLeftAligned;
     protected boolean mIsAboveIcon;
-    private int mGravity;
+    protected int mGravity;
 
     protected AnimatorSet mOpenCloseAnimator;
     protected boolean mDeferContainerRemoval;
+    protected boolean shouldScaleArrow = false;
 
     private final GradientDrawable mRoundedTop;
     private final GradientDrawable mRoundedBottom;
@@ -122,10 +123,10 @@
     private Runnable mOnCloseCallback = () -> { };
 
     // The rect string of the view that the arrow is attached to, in screen reference frame.
-    private int mArrowColor;
+    protected int mArrowColor;
     protected final List<LocalColorExtractor> mColorExtractors;
 
-    private final float mElevation;
+    protected final float mElevation;
     private final int mBackgroundColor;
 
     private final String mIterateChildrenTag;
@@ -729,6 +730,14 @@
         scale.setInterpolator(interpolator);
         animatorSet.play(scale);
 
+        if (shouldScaleArrow) {
+            Animator arrowScaleAnimator = ObjectAnimator.ofFloat(mArrow, View.SCALE_Y,
+                    scaleValues);
+            arrowScaleAnimator.setDuration(totalDuration);
+            arrowScaleAnimator.setInterpolator(interpolator);
+            animatorSet.play(arrowScaleAnimator);
+        }
+
         fadeInChildViews(this, alphaValues, childFadeStartDelay, childFadeDuration, animatorSet);
 
         return animatorSet;
diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
index e662d5c..436aa51 100644
--- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -78,6 +78,32 @@
         mPath.transform(pathTransform);
     }
 
+    /**
+     * Constructor for an arrow that points to the left or right.
+     *
+     * @param width        of the arrow.
+     * @param height       of the arrow.
+     * @param radius       of the tip of the arrow.
+     * @param isPointingLeft or not.
+     * @param color        to draw the triangle.
+     */
+    public RoundedArrowDrawable(float width, float height, float radius, boolean isPointingLeft,
+            int color) {
+        mPath = new Path();
+        mPaint = new Paint();
+        mPaint.setColor(color);
+        mPaint.setStyle(Paint.Style.FILL);
+        mPaint.setAntiAlias(true);
+
+        // Make the drawable with the triangle pointing down...
+        addDownPointingRoundedTriangleToPath(width, height, radius, mPath);
+
+        // ... then rotate it to the side it needs to point.
+        Matrix pathTransform = new Matrix();
+        pathTransform.setRotate(isPointingLeft ? 90 : -90, width * 0.5f, height * 0.5f);
+        mPath.transform(pathTransform);
+    }
+
     @Override
     public void draw(Canvas canvas) {
         canvas.drawPath(mPath, mPaint);