Add arrow view to deep shortcuts container.

- We create a trangular path, which we use as the view's
  outline (for the shadow) and as its PathShape background.
- We scale up the arrow as the container opens.

Bug: 28980830
Change-Id: I71e566552f063dfdb69067bb7e3cee659988f934
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 6897269..6bfd069 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -164,7 +164,9 @@
          of the shortcut container before the container is removed. -->
     <dimen name="deep_shortcuts_start_drag_threshold">35dp</dimen>
     <dimen name="deep_shortcut_icon_size">36dp</dimen>
-    <dimen name="deep_shortcuts_arrow_horizontal_offset">19dp</dimen>
     <dimen name="deep_shortcut_anim_translation_y">5dp</dimen>
+    <dimen name="deep_shortcuts_arrow_width">10dp</dimen>
+    <dimen name="deep_shortcuts_arrow_height">8dp</dimen>
+    <dimen name="deep_shortcuts_arrow_vertical_offset">-2dp</dimen>
 
 </resources>
diff --git a/src/com/android/launcher3/graphics/TriangleShape.java b/src/com/android/launcher3/graphics/TriangleShape.java
new file mode 100644
index 0000000..cce4e3c
--- /dev/null
+++ b/src/com/android/launcher3/graphics/TriangleShape.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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.graphics;
+
+import android.graphics.Outline;
+import android.graphics.Path;
+import android.graphics.drawable.shapes.PathShape;
+import android.support.annotation.NonNull;
+
+/**
+ * Wrapper around {@link android.graphics.drawable.shapes.PathShape}
+ * that creates a shape with a triangular path (pointing up or down).
+ */
+public class TriangleShape extends PathShape {
+    private Path mTriangularPath;
+
+    public TriangleShape(Path path, float stdWidth, float stdHeight) {
+        super(path, stdWidth, stdHeight);
+        mTriangularPath = path;
+    }
+
+    public static TriangleShape create(float width, float height, boolean isPointingUp) {
+        Path triangularPath = new Path();
+        if (isPointingUp) {
+            triangularPath.moveTo(0, height);
+            triangularPath.lineTo(width, height);
+            triangularPath.lineTo(width / 2, 0);
+            triangularPath.close();
+        } else {
+            triangularPath.moveTo(0, 0);
+            triangularPath.lineTo(width / 2, height);
+            triangularPath.lineTo(width, 0);
+            triangularPath.close();
+        }
+        return new TriangleShape(triangularPath, width, height);
+    }
+
+    @Override
+    public void getOutline(@NonNull Outline outline) {
+        outline.setConvexPath(mTriangularPath);
+    }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index f6fcdde..7cb2d43 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -89,11 +89,8 @@
 
     /**
      * Creates an animator to play when the shortcut container is being opened.
-     *
-     * @param animationIndex The index at which this animation will be started
-     *                       relative to other DeepShortcutView open animations.
      */
-    public Animator createOpenAnimation(int animationIndex, boolean isContainerAboveIcon) {
+    public Animator createOpenAnimation(long animationDelay, boolean isContainerAboveIcon) {
         final Resources res = getResources();
         setVisibility(INVISIBLE);
 
@@ -119,8 +116,7 @@
                 .scaleX(1).scaleY(1);
 
         openAnimation.playTogether(reveal, translationY, scale);
-        openAnimation.setStartDelay(animationIndex * res.getInteger(
-                R.integer.config_deepShortcutOpenStagger));
+        openAnimation.setStartDelay(animationDelay);
         openAnimation.setDuration(res.getInteger(R.integer.config_deepShortcutOpenDuration));
         openAnimation.setInterpolator(new DecelerateInterpolator());
         return openAnimation;
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
index 4cbbe97..c881c8c 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutsContainer.java
@@ -16,19 +16,24 @@
 
 package com.android.launcher3.shortcuts;
 
+import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Looper;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
@@ -44,6 +49,7 @@
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherViewPropertyAnimator;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutInfo;
 import com.android.launcher3.Utilities;
@@ -54,6 +60,7 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.graphics.ScaledPreviewProvider;
+import com.android.launcher3.graphics.TriangleShape;
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -88,6 +95,7 @@
     private Point mIconLastTouchPos = new Point();
     private boolean mIsLeftAligned;
     private boolean mIsAboveIcon;
+    private View mArrow;
 
     private boolean mSrcIconDragStarted;
 
@@ -210,27 +218,56 @@
     }
 
     private DeepShortcutView getShortcutAt(int index) {
+        if (!mIsAboveIcon) {
+            // Opening down, so arrow is the first view.
+            index++;
+        }
         return (DeepShortcutView) getChildAt(index);
     }
 
+    private int getShortcutCount() {
+        // All children except the arrow are shortcuts.
+        return getChildCount() - 1;
+    }
+
     private void animateOpen(BubbleTextView originalIcon) {
         orientAboutIcon(originalIcon);
+        final Resources resources = getResources();
+        final int arrowWidth = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_width);
+        final int arrowHeight = resources.getDimensionPixelSize(R.dimen.deep_shortcuts_arrow_height);
+        int iconWidth = originalIcon.getWidth() - originalIcon.getTotalPaddingLeft()
+                - originalIcon.getTotalPaddingRight();
+        iconWidth *= originalIcon.getScaleX();
+        final int arrowHorizontalOffset = iconWidth / 2 - arrowWidth / 2;
+        final int arrowVerticalOffset = resources.getDimensionPixelSize(
+                R.dimen.deep_shortcuts_arrow_vertical_offset);
+        mArrow = addArrowView(arrowHorizontalOffset, arrowVerticalOffset, arrowWidth, arrowHeight);
 
         setVisibility(View.VISIBLE);
 
         final AnimatorSet shortcutAnims = LauncherAnimUtils.createAnimatorSet();
-        final int numShortcuts = getChildCount();
-        final int arrowOffset = getResources().getDimensionPixelSize(
-                R.dimen.deep_shortcuts_arrow_horizontal_offset);
-        final int pivotX = mIsLeftAligned ? arrowOffset : getMeasuredWidth() - arrowOffset;
+        final int shortcutCount = getShortcutCount();
+        final int pivotX = mIsLeftAligned ? arrowHorizontalOffset
+                : getMeasuredWidth() - arrowHorizontalOffset;
         final int pivotY = getShortcutAt(0).getMeasuredHeight() / 2;
-        for (int i = 0; i < numShortcuts; i++) {
+        for (int i = 0; i < shortcutCount; i++) {
             DeepShortcutView deepShortcutView = getShortcutAt(i);
             deepShortcutView.setPivotX(pivotX);
             deepShortcutView.setPivotY(pivotY);
-            int animationIndex = mIsAboveIcon ? numShortcuts - i - 1 : i;
-            shortcutAnims.play(deepShortcutView.createOpenAnimation(animationIndex, mIsAboveIcon));
+            int animationIndex = mIsAboveIcon ? shortcutCount - i - 1 : i;
+            long animationDelay = animationIndex * getResources().getInteger(
+                    R.integer.config_deepShortcutOpenStagger);
+            shortcutAnims.play(deepShortcutView.createOpenAnimation(animationDelay, mIsAboveIcon));
         }
+        mArrow.setScaleX(0);
+        mArrow.setScaleY(0);
+        final long shortcutAnimDuration = shortcutAnims.getChildAnimations().get(0).getDuration();
+        final long arrowScaleDelay = shortcutAnimDuration / 6;
+        final long arrowScaleDuration = shortcutAnimDuration - arrowScaleDelay;
+        Animator arrowScale = new LauncherViewPropertyAnimator(mArrow).scaleX(1).scaleY(1);
+        arrowScale.setStartDelay(arrowScaleDelay);
+        arrowScale.setDuration(arrowScaleDuration);
+        shortcutAnims.play(arrowScale);
         shortcutAnims.start();
     }
 
@@ -245,8 +282,6 @@
      *
      * So we always align left if there is enough horizontal space
      * and align above if there is enough vertical space.
-     *
-     * TODO: draw arrow based on orientation.
      */
     private void orientAboutIcon(BubbleTextView icon) {
         int width = getMeasuredWidth();
@@ -285,6 +320,36 @@
         setY(y);
     }
 
+    /**
+     * Adds an arrow view pointing at the original icon.
+     * @param horizontalOffset the horizontal offset of the arrow, so that it
+     *                              points at the center of the original icon
+     */
+    private View addArrowView(int horizontalOffset, int verticalOffset, int width, int height) {
+        LinearLayout.LayoutParams layoutParams = new LayoutParams(width, height);
+        if (mIsLeftAligned) {
+            layoutParams.gravity = Gravity.LEFT;
+            layoutParams.leftMargin = horizontalOffset;
+        } else {
+            layoutParams.gravity = Gravity.RIGHT;
+            layoutParams.rightMargin = horizontalOffset;
+        }
+        if (mIsAboveIcon) {
+            layoutParams.topMargin = verticalOffset;
+        } else {
+            layoutParams.bottomMargin = verticalOffset;
+        }
+
+        View arrowView = new View(getContext());
+        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                width, height, !mIsAboveIcon));
+        arrowDrawable.getPaint().setColor(Color.WHITE);
+        arrowView.setBackground(arrowDrawable);
+        arrowView.setElevation(getElevation());
+        addView(arrowView, mIsAboveIcon ? getChildCount() : 0, layoutParams);
+        return arrowView;
+    }
+
     private void deferDrag(BubbleTextView originalIcon) {
         mDeferredDragIcon = originalIcon;
         showDragView(originalIcon);
@@ -339,6 +404,7 @@
         Utilities.translateEventCoordinates(this, mLauncher.getDragLayer(), ev);
         final int dragLayerX = (int) ev.getX();
         final int dragLayerY = (int) ev.getY();
+        int shortcutCount = getShortcutCount();
         if (action == MotionEvent.ACTION_MOVE) {
             if (mLastX != 0 || mLastY != 0) {
                 mDistanceDragged += Math.hypot(mLastX - x, mLastY - y);
@@ -347,6 +413,8 @@
             mLastY = y;
 
             if (shouldStartDeferredDrag((int) x, (int) y)) {
+            DeepShortcutView topShortcut = getShortcutAt(0);
+            DeepShortcutView bottomShortcut = getShortcutAt(shortcutCount - 1);
                 mSrcIconDragStarted = true;
                 cleanupDeferredDrag(true);
                 mDeferredDragIcon.getParent().requestDisallowInterceptTouchEvent(false);