Initial changes to allow dragging external shortcuts to launcher using the same InstallShortcut intent.

Change-Id: I21b57115429ed37d604084ae01308d1d3f33ee7e
diff --git a/src/com/android/launcher2/CellLayout.java b/src/com/android/launcher2/CellLayout.java
index 63da108..3c82290 100644
--- a/src/com/android/launcher2/CellLayout.java
+++ b/src/com/android/launcher2/CellLayout.java
@@ -16,7 +16,7 @@
 
 package com.android.launcher2;
 
-import com.android.launcher.R;
+import java.util.Arrays;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -26,6 +26,7 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.WallpaperManager;
+import android.content.ClipDescription;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -41,6 +42,7 @@
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
+import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewDebug;
@@ -49,7 +51,7 @@
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.LayoutAnimationController;
 
-import java.util.Arrays;
+import com.android.launcher.R;
 
 public class CellLayout extends ViewGroup implements Dimmable {
     static final String TAG = "CellLayout";
@@ -972,7 +974,11 @@
         final int oldDragCellX = mDragCell[0];
         final int oldDragCellY = mDragCell[1];
         final int[] nearest = findNearestVacantArea(originX, originY, spanX, spanY, v, mDragCell);
-        mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
+        if (v != null) {
+            mDragCenter.set(originX + (v.getWidth() / 2), originY + (v.getHeight() / 2));
+        } else {
+            mDragCenter.set(originX, originY);
+        }
 
         if (nearest != null && (nearest[0] != oldDragCellX || nearest[1] != oldDragCellY)) {
             // Find the top left corner of the rect the object will occupy
@@ -982,15 +988,17 @@
             int left = topLeft[0];
             int top = topLeft[1];
 
-            if (v.getParent() instanceof CellLayout) {
-                LayoutParams lp = (LayoutParams) v.getLayoutParams();
-                left += lp.leftMargin;
-                top += lp.topMargin;
-            }
+            if (v != null) {
+                if (v.getParent() instanceof CellLayout) {
+                    LayoutParams lp = (LayoutParams) v.getLayoutParams();
+                    left += lp.leftMargin;
+                    top += lp.topMargin;
+                }
 
-            // Offsets due to the size difference between the View and the dragOutline
-            left += (v.getWidth() - dragOutline.getWidth()) / 2;
-            top += (v.getHeight() - dragOutline.getHeight()) / 2;
+                // Offsets due to the size difference between the View and the dragOutline
+                left += (v.getWidth() - dragOutline.getWidth()) / 2;
+                top += (v.getHeight() - dragOutline.getHeight()) / 2;
+            }
 
             final int oldIndex = mDragOutlineCurrent;
             mDragOutlineAnims[oldIndex].animateOut();
@@ -1271,7 +1279,7 @@
      * It may have begun over this layout (in which case onDragChild is called first),
      * or it may have begun on another layout.
      */
-    void onDragEnter(View dragView) {
+    void onDragEnter() {
         if (!mDragging) {
             // Fade in the drag indicators
             if (mCrosshairsAnimator != null) {
diff --git a/src/com/android/launcher2/InstallShortcutReceiver.java b/src/com/android/launcher2/InstallShortcutReceiver.java
index caeb12b..8d72531 100644
--- a/src/com/android/launcher2/InstallShortcutReceiver.java
+++ b/src/com/android/launcher2/InstallShortcutReceiver.java
@@ -26,7 +26,7 @@
 import com.android.launcher.R;
 
 public class InstallShortcutReceiver extends BroadcastReceiver {
-    private static final String ACTION_INSTALL_SHORTCUT =
+    public static final String ACTION_INSTALL_SHORTCUT =
             "com.android.launcher.action.INSTALL_SHORTCUT";
 
     private final int[] mCoordinates = new int[2];
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 444357e..346e472 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1144,6 +1144,10 @@
         return mAppWidgetHost;
     }
 
+    public LauncherModel getModel() {
+        return mModel;
+    }
+
     void closeSystemDialogs() {
         getWindow().closeAllPanels();
 
diff --git a/src/com/android/launcher2/LauncherModel.java b/src/com/android/launcher2/LauncherModel.java
index 1e58ca0..179a5d5 100644
--- a/src/com/android/launcher2/LauncherModel.java
+++ b/src/com/android/launcher2/LauncherModel.java
@@ -1513,14 +1513,25 @@
     ShortcutInfo addShortcut(Context context, Intent data,
             int screen, int cellX, int cellY, boolean notify) {
 
-        final ShortcutInfo info = infoFromShortcutIntent(context, data);
+        final ShortcutInfo info = infoFromShortcutIntent(context, data, null);
         addItemToDatabase(context, info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 screen, cellX, cellY, notify);
 
         return info;
     }
 
-    private ShortcutInfo infoFromShortcutIntent(Context context, Intent data) {
+    /**
+     * Ensures that a given shortcut intent actually has all the fields that we need to create a
+     * proper ShortcutInfo.
+     */
+    boolean validateShortcutIntent(Intent data) {
+        // We don't require Intent.EXTRA_SHORTCUT_ICON, since we can pull a default fallback icon
+        return InstallShortcutReceiver.ACTION_INSTALL_SHORTCUT.equals(data.getAction()) &&
+                (data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT) != null) &&
+                (data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME) != null);
+    }
+
+    ShortcutInfo infoFromShortcutIntent(Context context, Intent data, Bitmap fallbackIcon) {
         Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT);
         String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME);
         Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON);
@@ -1553,8 +1564,12 @@
         final ShortcutInfo info = new ShortcutInfo();
 
         if (icon == null) {
-            icon = getFallbackIcon();
-            info.usingFallbackIcon = true;
+            if (fallbackIcon != null) {
+                icon = fallbackIcon;
+            } else {
+                icon = getFallbackIcon();
+                info.usingFallbackIcon = true;
+            }
         }
         info.setIcon(icon);
 
diff --git a/src/com/android/launcher2/Workspace.java b/src/com/android/launcher2/Workspace.java
index ce613f1..07faed1 100644
--- a/src/com/android/launcher2/Workspace.java
+++ b/src/com/android/launcher2/Workspace.java
@@ -16,7 +16,8 @@
 
 package com.android.launcher2;
 
-import com.android.launcher.R;
+import java.util.ArrayList;
+import java.util.HashSet;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
@@ -27,6 +28,8 @@
 import android.app.WallpaperManager;
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.ClipDescription;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -36,8 +39,11 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
@@ -45,12 +51,13 @@
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.TextView;
+import android.widget.Toast;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import com.android.launcher.R;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -144,6 +151,9 @@
     private final Rect mTempRect = new Rect();
     private final int[] mTempXY = new int[2];
 
+    // Paint used to draw external drop outline
+    private final Paint mExternalDragOutlinePaint = new Paint();
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -193,6 +203,7 @@
         Launcher.setScreen(mCurrentPage);
         LauncherApplication app = (LauncherApplication)context.getApplicationContext();
         mIconCache = app.getIconCache();
+        mExternalDragOutlinePaint.setAntiAlias(true);
 
         mUnshrinkAnimationListener = new AnimatorListenerAdapter() {
             public void onAnimationStart(Animator animation) {
@@ -984,6 +995,29 @@
     }
 
     /**
+     * Creates a drag outline to represent a drop (that we don't have the actual information for
+     * yet).  May be changed in the future to alter the drop outline slightly depending on the
+     * clip description mime data.
+     */
+    private Bitmap createExternalDragOutline(Canvas canvas, int padding) {
+        Resources r = getResources();
+        final int outlineColor = r.getColor(R.color.drag_outline_color);
+        final int iconWidth = r.getDimensionPixelSize(R.dimen.workspace_cell_width);
+        final int iconHeight = r.getDimensionPixelSize(R.dimen.workspace_cell_height);
+        final int rectRadius = r.getDimensionPixelSize(R.dimen.external_drop_icon_rect_radius);
+        final int inset = (int) (Math.min(iconWidth, iconHeight) * 0.2f);
+        final Bitmap b = Bitmap.createBitmap(
+                iconWidth + padding, iconHeight + padding, Bitmap.Config.ARGB_8888);
+
+        canvas.setBitmap(b);
+        canvas.drawRoundRect(new RectF(inset, inset, iconWidth - inset, iconHeight - inset),
+                rectRadius, rectRadius, mExternalDragOutlinePaint);
+        mOutlineHelper.applyExpensiveOuterOutline(b, canvas, outlineColor, true);
+
+        return b;
+    }
+
+    /**
      * Returns a new bitmap to show when the given View is being dragged around.
      * Responsibility for the bitmap is transferred to the caller.
      */
@@ -1136,7 +1170,7 @@
 
         if (!mIsSmall) {
             mDragTargetLayout = getCurrentDropLayout();
-            mDragTargetLayout.onDragEnter(dragView);
+            mDragTargetLayout.onDragEnter();
             showOutlines();
         }
     }
@@ -1182,6 +1216,88 @@
         return null;
     }
 
+    /**
+     * Global drag and drop handler
+     */
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        final CellLayout layout = (CellLayout) getChildAt(mCurrentPage);
+        final int[] pos = new int[2];
+        layout.getLocationOnScreen(pos);
+        // We need to offset the drag coordinates to layout coordinate space
+        final int x = (int) event.getX() - pos[0];
+        final int y = (int) event.getY() - pos[1];
+
+        switch (event.getAction()) {
+        case DragEvent.ACTION_DRAG_STARTED:
+            // Check if we have enough space on this screen to add a new shortcut
+            if (!layout.findCellForSpan(pos, 1, 1)) {
+                Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
+                        Toast.LENGTH_SHORT).show();
+                return false;
+            }
+
+            ClipDescription desc = event.getClipDescription();
+            if (desc.filterMimeTypes(ClipDescription.MIMETYPE_TEXT_INTENT) != null) {
+                // Create the drag outline
+                // We need to add extra padding to the bitmap to make room for the glow effect
+                final Canvas canvas = new Canvas();
+                final int bitmapPadding = HolographicOutlineHelper.OUTER_BLUR_RADIUS;
+                mDragOutline = createExternalDragOutline(canvas, bitmapPadding);
+
+                // Show the current page outlines to indicate that we can accept this drop
+                showOutlines();
+                layout.setHover(true);
+                layout.onDragEnter();
+                layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
+
+                return true;
+            }
+            break;
+        case DragEvent.ACTION_DRAG_LOCATION:
+            // Visualize the drop location
+            layout.visualizeDropLocation(null, mDragOutline, x, y, 1, 1);
+            return true;
+        case DragEvent.ACTION_DROP:
+            // Check if we have enough space on this screen to add a new shortcut
+            if (!layout.findCellForSpan(pos, 1, 1)) {
+                Toast.makeText(mContext, mContext.getString(R.string.out_of_space),
+                        Toast.LENGTH_SHORT).show();
+                return false;
+            }
+
+            // Try and add any shortcuts
+            int newDropCount = 0;
+            final LauncherModel model = mLauncher.getModel();
+            final ClipData data = event.getClipData();
+            final int itemCount = data.getItemCount();
+            for (int i = 0; i < itemCount; ++i) {
+                final Intent intent = data.getItem(i).getIntent();
+                if (intent != null && model.validateShortcutIntent(intent)) {
+                    ShortcutInfo info = model.infoFromShortcutIntent(mContext, intent, data.
+                            getIcon());
+                    onDropExternal(x, y, info, layout);
+                    newDropCount++;
+                }
+            }
+
+            // Show error message if we couldn't accept any of the items
+            if (newDropCount <= 0) {
+                Toast.makeText(mContext, "Only Shortcut Intents accepted.",
+                        Toast.LENGTH_SHORT).show();
+            }
+
+            return true;
+        case DragEvent.ACTION_DRAG_ENDED:
+            // Hide the page outlines after the drop
+            layout.setHover(false);
+            layout.onDragExit();
+            hideOutlines();
+            return true;
+        }
+        return super.onDragEvent(event);
+    }
+
     /*
     *
     * Convert the 2D coordinate xy from the parent View's coordinate space to this CellLayout's
@@ -1382,7 +1498,7 @@
                     if (mDragTargetLayout != null) {
                         mDragTargetLayout.onDragExit();
                     }
-                    layout.onDragEnter(dragView);
+                    layout.onDragEnter();
                     mDragTargetLayout = layout;
                 }