Adding single/multi selection mode to AllAppsPagedView (action bar disabled).

Also adding check to prevent scrolling mode from continuing on touch down.

Change-Id: I744f2c1f6bc659219145b52b15a7ea176853ec7c
diff --git a/src/com/android/launcher2/AllAppsPagedView.java b/src/com/android/launcher2/AllAppsPagedView.java
index a673304..0aa7724 100644
--- a/src/com/android/launcher2/AllAppsPagedView.java
+++ b/src/com/android/launcher2/AllAppsPagedView.java
@@ -22,10 +22,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.TypedArray;
+import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.ActionMode;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
 import android.view.View;
 import android.view.animation.AnimationUtils;
+import android.widget.Checkable;
 import android.widget.TextView;
 
 import com.android.launcher.R;
@@ -35,7 +40,8 @@
  * with all of the user's applications.
  */
 public class AllAppsPagedView extends PagedView
-        implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource {
+        implements AllAppsView, View.OnClickListener, View.OnLongClickListener, DragSource,
+        DropTarget, ActionMode.Callback {
 
     private static final String TAG = "AllAppsPagedView";
     private static final boolean DEBUG = false;
@@ -127,6 +133,8 @@
         if (!isVisible()) {
             setVisibility(View.GONE);
             mZoom = 0.0f;
+
+            endChoiceMode();
         } else {
             mZoom = 1.0f;
         }
@@ -138,7 +146,7 @@
     private int getChildIndexForGrandChild(View v) {
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; ++i) {
-            PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+            final PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
             if (layout.indexOfChild(v) > -1) {
                 return i;
             }
@@ -148,6 +156,22 @@
 
     @Override
     public void onClick(View v) {
+        // if we are already in a choice mode, then just change the selection
+        if (v instanceof Checkable) {
+            if (!isChoiceMode(CHOICE_MODE_NONE)) {
+                if (isChoiceMode(CHOICE_MODE_SINGLE)) {
+                    // reset all the previously checked items if in single selection mode
+                    resetCheckedGrandchildren();
+                }
+
+                // then toggle this one
+                Checkable c = (Checkable) v;
+                c.toggle();
+                return;
+            }
+        }
+
+        // otherwise continue and launch the application
         int childIndex = getChildIndexForGrandChild(v);
         if (childIndex == getCurrentPage()) {
             final ApplicationInfo app = (ApplicationInfo) v.getTag();
@@ -159,6 +183,8 @@
                     mLauncher.startActivitySafely(app.intent, app);
                 }
             });
+
+            endChoiceMode();
         }
     }
 
@@ -168,6 +194,24 @@
             return false;
         }
 
+        /* Uncomment this to enable selection mode with the action bar
+
+        // start the choice mode, and select the item that was long-pressed
+        if (isChoiceMode(CHOICE_MODE_NONE)) {
+            startChoiceMode(CHOICE_MODE_SINGLE, this);
+        }
+
+        if (v instanceof Checkable) {
+            // In preparation for drag, we always reset the checked grand children regardless of
+            // what choice mode we are in
+            resetCheckedGrandchildren();
+
+            // Toggle the selection on the dragged app
+            Checkable c = (Checkable) v;
+            c.toggle();
+        }
+         */
+
         ApplicationInfo app = (ApplicationInfo) v.getTag();
         app = new ApplicationInfo(app);
 
@@ -177,7 +221,10 @@
 
     @Override
     public void onDropCompleted(View target, boolean success) {
-        // do nothing
+        // close the choice action mode if we have a proper drop
+        if (target != this) {
+            endChoiceMode();
+        }
     }
 
     @Override
@@ -341,4 +388,63 @@
             params.cellY = index / mCellCountX;
         }
     }
+
+    @Override
+    public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+        mDragController.addDropTarget(this);
+
+        // REST TO BE IMPLEMENTED BY PAT
+        mode.setTitle("Customization title goes here");
+        return true;
+    }
+
+    @Override
+    public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+        return true;
+    }
+
+    @Override
+    public void onDestroyActionMode(ActionMode mode) {
+        mDragController.removeDropTarget(this);
+        endChoiceMode();
+    }
+
+    @Override
+    public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+        // TO BE IMPLEMENTED BY PAT
+        // get the checked grandchild, and handle the action here
+        return false;
+    }
+
+    /*
+     * We don't actually use AllAppsPagedView as a drop target... it's only used to intercept a drop
+     * to the workspace.
+     */
+    @Override
+    public boolean acceptDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {
+        return false;
+    }
+    @Override
+    public Rect estimateDropLocation(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo, Rect recycle) {
+        return null;
+    }
+    @Override
+    public DropTarget getDropTargetDelegate(DragSource source, int x, int y, int xOffset,
+            int yOffset, DragView dragView, Object dragInfo) {
+        return null;
+    }
+    @Override
+    public void onDragEnter(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {}
+    @Override
+    public void onDragExit(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {}
+    @Override
+    public void onDragOver(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {}
+    @Override
+    public void onDrop(DragSource source, int x, int y, int xOffset, int yOffset,
+            DragView dragView, Object dragInfo) {}
 }
diff --git a/src/com/android/launcher2/PagedView.java b/src/com/android/launcher2/PagedView.java
index 5052a59..8e0203b 100644
--- a/src/com/android/launcher2/PagedView.java
+++ b/src/com/android/launcher2/PagedView.java
@@ -19,7 +19,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -27,6 +26,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.view.ActionMode;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -36,6 +37,7 @@
 import android.view.animation.Animation;
 import android.view.animation.Animation.AnimationListener;
 import android.view.animation.AnimationUtils;
+import android.widget.Checkable;
 import android.widget.Scroller;
 
 import com.android.launcher.R;
@@ -51,6 +53,7 @@
     // the min drag distance for a fling to register, to prevent random page shifts
     private static final int MIN_LENGTH_FOR_FLING = 50;
 
+    private static final int PAGE_SNAP_ANIMATION_DURATION = 1000;
     protected static final float NANOTIME_DIV = 1000000000.0f;
 
     // the velocity at which a fling gesture will cause us to snap to the next page
@@ -96,6 +99,14 @@
     private ArrayList<Boolean> mDirtyPageContent;
     private boolean mDirtyPageAlpha;
 
+    // choice modes
+    protected static final int CHOICE_MODE_NONE = 0;
+    protected static final int CHOICE_MODE_SINGLE = 1;
+    // Multiple selection mode is not supported by all Launcher actions atm
+    protected static final int CHOICE_MODE_MULTIPLE = 2;
+    private int mChoiceMode;
+    private ActionMode mActionMode;
+
     protected PagedViewIconCache mPageViewIconCache;
 
     // If true, syncPages and syncPageItems will be called to refresh pages
@@ -153,6 +164,7 @@
 
     public PagedView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        mChoiceMode = CHOICE_MODE_NONE;
 
         setHapticFeedbackEnabled(false);
         init();
@@ -530,7 +542,14 @@
                  * otherwise don't.  mScroller.isFinished should be false when
                  * being flinged.
                  */
-                mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
+                final int xDist = (mScroller.getFinalX() - mScroller.getCurrX());
+                final boolean finishedScrolling = (mScroller.isFinished() || xDist < mTouchSlop);
+                if (finishedScrolling) {
+                    mTouchState = TOUCH_STATE_REST;
+                    mScroller.abortAnimation();
+                } else {
+                    mTouchState = TOUCH_STATE_SCROLLING;
+                }
 
                 // check if this can be the beginning of a tap on the side of the pages
                 // to scroll the current page
@@ -819,7 +838,7 @@
                 minDistanceFromScreenCenterIndex = i;
             }
         }
-        snapToPage(minDistanceFromScreenCenterIndex, 1000);
+        snapToPage(minDistanceFromScreenCenterIndex, PAGE_SNAP_ANIMATION_DURATION);
     }
 
     protected void snapToPageWithVelocity(int whichPage, int velocity) {
@@ -829,7 +848,7 @@
     }
 
     protected void snapToPage(int whichPage) {
-        snapToPage(whichPage, 1000);
+        snapToPage(whichPage, PAGE_SNAP_ANIMATION_DURATION);
     }
 
     protected void snapToPage(int whichPage, int duration) {
@@ -982,6 +1001,50 @@
         }
     }
 
+    protected void startChoiceMode(int mode, ActionMode.Callback callback) {
+        // StartActionMode may call through toendChoiceMode, so we should do this first
+        mActionMode = startActionMode(callback);
+        mChoiceMode = mode;
+    }
+
+    protected void endChoiceMode() {
+        if (!isChoiceMode(CHOICE_MODE_NONE)) {
+            mActionMode.finish();
+            mActionMode = null;
+            mChoiceMode = CHOICE_MODE_NONE;
+            resetCheckedGrandchildren();
+        }
+    }
+
+    protected boolean isChoiceMode(int mode) {
+        return mChoiceMode == mode;
+    }
+
+    protected ArrayList<Checkable> getCheckedGrandchildren() {
+        ArrayList<Checkable> checked = new ArrayList<Checkable>();
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; ++i) {
+            final PagedViewCellLayout layout = (PagedViewCellLayout) getChildAt(i);
+            final int grandChildCount = layout.getChildCount();
+            for (int j = 0; j < grandChildCount; ++j) {
+                final View v = layout.getChildAt(j);
+                if (v instanceof Checkable) {
+                    checked.add((Checkable) v);
+                }
+            }
+        }
+        return checked;
+    }
+
+    protected void resetCheckedGrandchildren() {
+        // loop through children, and set all of their children to _not_ be checked
+        final ArrayList<Checkable> checked = getCheckedGrandchildren();
+        for (int i = 0; i < checked.size(); ++i) {
+            final Checkable c = checked.get(i);
+            c.setChecked(false);
+        }
+    }
+
     /**
      * This method is called ONLY to synchronize the number of pages that the paged view has.
      * To actually fill the pages with information, implement syncPageItems() below.  It is
diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java
index e227569..b8cc206 100644
--- a/src/com/android/launcher2/PagedViewIcon.java
+++ b/src/com/android/launcher2/PagedViewIcon.java
@@ -40,17 +40,19 @@
 import com.android.launcher2.PagedView.PagedViewIconCache;
 
 class HolographicOutlineHelper {
+    private float mDensity;
     private final Paint mHolographicPaint = new Paint();
     private final Paint mBlurPaint = new Paint();
     private final Paint mErasePaint = new Paint();
-    private static final Matrix mIdentity = new Matrix();
-    private static final float STROKE_WIDTH = 6.0f;
+    private static final Matrix mIdentity = new Matrix();;
     private static final float BLUR_FACTOR = 3.5f;
 
+    public static final float DEFAULT_STROKE_WIDTH = 6.0f;
     public static final int HOLOGRAPHIC_BLUE = 0xFF6699FF;
-    public static final int HOLOGRAPHIC_GREEN = 0xFF66FF66;
+    public static final int HOLOGRAPHIC_GREEN = 0xFF51E633;
 
     HolographicOutlineHelper(float density) {
+        mDensity = density;
         mHolographicPaint.setColor(HOLOGRAPHIC_BLUE);
         mHolographicPaint.setFilterBitmap(true);
         mHolographicPaint.setAntiAlias(true);
@@ -101,13 +103,14 @@
     /**
      * Applies an outline to whatever is currently drawn in the specified bitmap.
      */
-    void applyOutline(Bitmap srcDst, Canvas srcDstCanvas, PointF offset) {
+    void applyOutline(Bitmap srcDst, Canvas srcDstCanvas, float strokeWidth, PointF offset) {
+        strokeWidth *= mDensity;
         Bitmap mask = srcDst.extractAlpha();
         Matrix m = new Matrix();
         final int width = srcDst.getWidth();
         final int height = srcDst.getHeight();
-        float xScale = STROKE_WIDTH*2.0f/width;
-        float yScale = STROKE_WIDTH*2.0f/height;
+        float xScale = strokeWidth*2.0f/width;
+        float yScale = strokeWidth*2.0f/height;
         m.preScale(1+xScale, 1+yScale, (width / 2.0f) + offset.x,
                 (height / 2.0f) + offset.y);
 
@@ -138,6 +141,7 @@
     // holographic outline
     private final Paint mPaint = new Paint();
     private static HolographicOutlineHelper sHolographicOutlineHelper;
+    private Bitmap mCheckedOutline;
     private Bitmap mHolographicOutline;
     private Canvas mHolographicOutlineCanvas;
     private boolean mIsHolographicUpdatePass;
@@ -207,6 +211,13 @@
         super.setAlpha(viewAlpha);
     }
 
+    public void invalidateCheckedImage() {
+        if (mCheckedOutline != null) {
+            mCheckedOutline.recycle();
+            mCheckedOutline = null;
+        }
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -229,10 +240,11 @@
             draw(mHolographicOutlineCanvas);
             sHolographicOutlineHelper.setColor(HolographicOutlineHelper.HOLOGRAPHIC_BLUE);
             sHolographicOutlineHelper.applyOutline(mHolographicOutline, mHolographicOutlineCanvas,
-                    offset);
+                    HolographicOutlineHelper.DEFAULT_STROKE_WIDTH, offset);
             sHolographicOutlineHelper.applyBlur(mHolographicOutline, mHolographicOutlineCanvas);
             mIsHolographicUpdatePass = false;
             mIconCache.addOutline(mIconCacheKey, mHolographicOutline);
+            mHolographicOutlineCanvas = null;
         }
     }
 
@@ -254,9 +266,17 @@
             }
         }
 
-        if (!mIsHolographicUpdatePass && mHolographicOutline != null && mHolographicAlpha > 0) {
-            mPaint.setAlpha(mHolographicAlpha);
-            canvas.drawBitmap(mHolographicOutline, 0, 0, mPaint);
+        // draw any blended overlays
+        if (!mIsHolographicUpdatePass) {
+            if (mCheckedOutline == null) {
+                if (mHolographicOutline != null && mHolographicAlpha > 0) {
+                    mPaint.setAlpha(mHolographicAlpha);
+                    canvas.drawBitmap(mHolographicOutline, 0, 0, mPaint);
+                }
+            } else {
+                mPaint.setAlpha(255);
+                canvas.drawBitmap(mCheckedOutline, 0, 0, mPaint);
+            }
         }
     }
 
@@ -267,8 +287,32 @@
 
     @Override
     public void setChecked(boolean checked) {
-        mIsChecked = checked;
-        invalidate();
+        if (mIsChecked != checked) {
+            mIsChecked = checked;
+
+            if (mIsChecked) {
+                final PointF offset = new PointF(0,
+                        -(getCompoundPaddingBottom() + getCompoundPaddingTop())/2.0f);
+
+                // set a flag to indicate that we are going to draw the view at full alpha with the text
+                // clipped for the generation of the holographic icon
+                mIsHolographicUpdatePass = true;
+                mCheckedOutline = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(),
+                        Bitmap.Config.ARGB_8888);
+                mHolographicOutlineCanvas = new Canvas(mCheckedOutline);
+                mHolographicOutlineCanvas.concat(getMatrix());
+                draw(mHolographicOutlineCanvas);
+                sHolographicOutlineHelper.setColor(HolographicOutlineHelper.HOLOGRAPHIC_GREEN);
+                sHolographicOutlineHelper.applyOutline(mCheckedOutline, mHolographicOutlineCanvas,
+                        HolographicOutlineHelper.DEFAULT_STROKE_WIDTH + 1.0f, offset);
+                mIsHolographicUpdatePass = false;
+                mHolographicOutlineCanvas = null;
+            } else {
+                invalidateCheckedImage();
+            }
+
+            invalidate();
+        }
     }
 
     @Override