Updating the icon click feedback

> Using BubbleTextView everywhere, removed PagedIconView
> There is a brightness feedback on touch and shadow
feedback on click, until app launches

issue: 16878374
Change-Id: I3dc1149a123c8a75feca6210948398bf2187f1f2
diff --git a/res/layout/apps_customize_application.xml b/res/layout/apps_customize_application.xml
index 17f4a8e..c56cdf3 100644
--- a/res/layout/apps_customize_application.xml
+++ b/res/layout/apps_customize_application.xml
@@ -14,10 +14,8 @@
      limitations under the License.
 -->
 
-<com.android.launcher3.PagedViewIcon
+<com.android.launcher3.BubbleTextView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3"
-
     style="@style/WorkspaceIcon.AppsCustomize"
     android:id="@+id/application_icon"
     android:focusable="true" />
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 552e84c..0db60c9 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -106,7 +106,6 @@
     <declare-styleable name="BubbleTextView">
         <!-- A spacing override for the icons within a page -->
         <attr name="customShadows" format="boolean" />
-        <attr name="glowColor" format="color" />
     </declare-styleable>
 
     <!-- AppsCustomizePagedView specific attributes.  These attributes are used to
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 5e9c6ec..8aa2184 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -34,7 +34,6 @@
 
     <color name="quantum_panel_text_color">#FF666666</color>
     <color name="quantum_panel_text_shadow_color">#FFC4C4C4</color>
-    <color name="folder_items_glow_color">#FFCCCCCC</color>
     <color name="outline_color">#FFFFFFFF</color>
     <color name="widget_text_panel">#FF374248</color>
 
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 6079eee..0569306 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -91,7 +91,8 @@
         <item name="android:shadowRadius">2.0</item>
         <item name="android:shadowDx">0</item>
         <item name="android:shadowDy">2</item>
-        <item name="android:shadowColor">#FFC4C4C4</item>
+        <item name="android:shadowColor">@color/quantum_panel_text_shadow_color</item>
+        <item name="customShadows">false</item>
     </style>
 
     <style name="WorkspaceIcon.Folder">
@@ -103,7 +104,6 @@
         <item name="android:shadowDy">2</item>
 
         <item name="customShadows">false</item>
-        <item name="glowColor">@color/folder_items_glow_color</item>
     </style>
 
     <style name="SearchDropTargetBar">
diff --git a/src/com/android/launcher3/AppsCustomizePagedView.java b/src/com/android/launcher3/AppsCustomizePagedView.java
index b2228f7..1ab11ee 100644
--- a/src/com/android/launcher3/AppsCustomizePagedView.java
+++ b/src/com/android/launcher3/AppsCustomizePagedView.java
@@ -44,7 +44,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
 import android.widget.GridLayout;
 import android.widget.ImageView;
 import android.widget.Toast;
@@ -144,8 +143,7 @@
  */
 public class AppsCustomizePagedView extends PagedViewWithDraggableItems implements
         View.OnClickListener, View.OnKeyListener, DragSource,
-        PagedViewIcon.PressedCallback, PagedViewWidget.ShortPressListener,
-        LauncherTransitionable {
+        PagedViewWidget.ShortPressListener, LauncherTransitionable {
     static final String TAG = "AppsCustomizePagedView";
 
     private static Rect sTmpRect = new Rect();
@@ -167,7 +165,6 @@
 
     // Save and Restore
     private int mSaveInstanceStateItemIndex = -1;
-    private PagedViewIcon mPressedIcon;
 
     // Content
     private ArrayList<AppInfo> mApps;
@@ -444,39 +441,29 @@
     @Override
     public void onClick(View v) {
         // When we have exited all apps or are in transition, disregard clicks
-        if (!mLauncher.isAllAppsVisible() ||
-                mLauncher.getWorkspace().isSwitchingState()) return;
+        if (!mLauncher.isAllAppsVisible()
+                || mLauncher.getWorkspace().isSwitchingState()
+                || !(v instanceof PagedViewWidget)) return;
 
-        if (v instanceof PagedViewIcon) {
-            // Animate some feedback to the click
-            final AppInfo appInfo = (AppInfo) v.getTag();
-
-            // Lock the drawable state to pressed until we return to Launcher
-            if (mPressedIcon != null) {
-                mPressedIcon.lockDrawableState();
-            }
-            mLauncher.onClickPagedViewIcon(v, appInfo);
-        } else if (v instanceof PagedViewWidget) {
-            // Let the user know that they have to long press to add a widget
-            if (mWidgetInstructionToast != null) {
-                mWidgetInstructionToast.cancel();
-            }
-            mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
-                Toast.LENGTH_SHORT);
-            mWidgetInstructionToast.show();
-
-            // Create a little animation to show that the widget can move
-            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
-            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
-            AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
-            ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
-            tyuAnim.setDuration(125);
-            ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
-            tydAnim.setDuration(100);
-            bounce.play(tyuAnim).before(tydAnim);
-            bounce.setInterpolator(new AccelerateInterpolator());
-            bounce.start();
+        // Let the user know that they have to long press to add a widget
+        if (mWidgetInstructionToast != null) {
+            mWidgetInstructionToast.cancel();
         }
+        mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
+            Toast.LENGTH_SHORT);
+        mWidgetInstructionToast.show();
+
+        // Create a little animation to show that the widget can move
+        float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
+        final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
+        AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
+        ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
+        tyuAnim.setDuration(125);
+        ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
+        tydAnim.setDuration(100);
+        bounce.play(tyuAnim).before(tydAnim);
+        bounce.setInterpolator(new AccelerateInterpolator());
+        bounce.start();
     }
 
     public boolean onKey(View v, int keyCode, KeyEvent event) {
@@ -492,7 +479,6 @@
     }
 
     private void beginDraggingApplication(View v) {
-        mLauncher.getWorkspace().onDragStartedWithItem(v);
         mLauncher.getWorkspace().beginDragShared(v, this);
     }
 
@@ -726,7 +712,7 @@
     protected boolean beginDragging(final View v) {
         if (!super.beginDragging(v)) return false;
 
-        if (v instanceof PagedViewIcon) {
+        if (v instanceof BubbleTextView) {
             beginDraggingApplication(v);
         } else if (v instanceof PagedViewWidget) {
             if (!beginDraggingWidget(v)) {
@@ -741,9 +727,6 @@
             public void run() {
                 // We don't enter spring-loaded mode if the drag has been cancelled
                 if (mLauncher.getDragController().isDragging()) {
-                    // Reset the alpha on the dragged icon before we drag
-                    resetDrawableState();
-
                     // Go into spring loaded mode (must happen before we startDrag())
                     mLauncher.enterSpringLoadedDragMode();
                 }
@@ -992,10 +975,10 @@
         ArrayList<Bitmap> images = new ArrayList<Bitmap>();
         for (int i = startIndex; i < endIndex; ++i) {
             AppInfo info = mApps.get(i);
-            PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
+            BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                     R.layout.apps_customize_application, layout, false);
-            icon.applyFromApplicationInfo(info, true, this);
-            icon.setOnClickListener(this);
+            icon.applyFromApplicationInfo(info);
+            icon.setOnClickListener(mLauncher);
             icon.setOnLongClickListener(this);
             icon.setOnTouchListener(this);
             icon.setOnKeyListener(this);
@@ -1559,23 +1542,6 @@
         cancelAllTasks();
     }
 
-    @Override
-    public void iconPressed(PagedViewIcon icon) {
-        // Reset the previously pressed icon and store a reference to the pressed icon so that
-        // we can reset it on return to Launcher (in Launcher.onResume())
-        if (mPressedIcon != null) {
-            mPressedIcon.resetDrawableState();
-        }
-        mPressedIcon = icon;
-    }
-
-    public void resetDrawableState() {
-        if (mPressedIcon != null) {
-            mPressedIcon.resetDrawableState();
-            mPressedIcon = null;
-        }
-    }
-
     /*
      * We load an extra page on each side to prevent flashes from scrolling and loading of the
      * widget previews in the background with the AsyncTasks.
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 5c2bb99..869b0ac 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -23,14 +23,13 @@
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Rect;
 import android.graphics.Region;
-import android.graphics.Region.Op;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.TypedValue;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
@@ -44,12 +43,11 @@
 
     private static SparseArray<Theme> sPreloaderThemes = new SparseArray<>(2);
 
-    static final float SHADOW_LARGE_RADIUS = 4.0f;
-    static final float SHADOW_SMALL_RADIUS = 1.75f;
-    static final float SHADOW_Y_OFFSET = 2.0f;
-    static final int SHADOW_LARGE_COLOUR = 0xDD000000;
-    static final int SHADOW_SMALL_COLOUR = 0xCC000000;
-    static final float PADDING_H = 8.0f;
+    private static final float SHADOW_LARGE_RADIUS = 4.0f;
+    private static final float SHADOW_SMALL_RADIUS = 1.75f;
+    private static final float SHADOW_Y_OFFSET = 2.0f;
+    private static final int SHADOW_LARGE_COLOUR = 0xDD000000;
+    private static final int SHADOW_SMALL_COLOUR = 0xCC000000;
     static final float PADDING_V = 3.0f;
 
     private static final String TAG = "BubbleTextView";
@@ -57,14 +55,7 @@
     private static final boolean DEBUG = false;
 
     private HolographicOutlineHelper mOutlineHelper;
-    private final Canvas mTempCanvas = new Canvas();
-    private final Rect mTempRect = new Rect();
-    private boolean mDidInvalidateForPressedState;
-    private Bitmap mPressedOrFocusedBackground;
-    private int mFocusedOutlineColor;
-    private int mFocusedGlowColor;
-    private int mPressedOutlineColor;
-    private int mPressedGlowColor;
+    private Bitmap mPressedBackground;
 
     private float mSlop;
 
@@ -72,14 +63,15 @@
     private final boolean mCustomShadowsEnabled;
     private boolean mIsTextVisible;
 
+    // TODO: Remove custom background handling code, as no instance of BubbleTextView use any
+    // background.
     private boolean mBackgroundSizeChanged;
     private final Drawable mBackground;
 
     private boolean mStayPressed;
+    private boolean mIgnorePressedStateChange;
     private CheckLongPressHelper mLongPressHelper;
 
-    private CharSequence mDefaultText = "";
-
     public BubbleTextView(Context context) {
         this(context, null, 0);
     }
@@ -91,11 +83,8 @@
     public BubbleTextView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
-        Resources res = context.getResources();
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
-        setGlowColor(a.getColor(R.styleable.BubbleTextView_glowColor,
-                res.getColor(R.color.outline_color)));
         mCustomShadowsEnabled = a.getBoolean(R.styleable.BubbleTextView_customShadows, true);
         a.recycle();
 
@@ -143,6 +132,7 @@
         if (info.contentDescription != null) {
             setContentDescription(info.contentDescription);
         }
+        setText(info.title);
         setTag(info);
 
         if (info.wasPromise) {
@@ -150,6 +140,22 @@
         }
     }
 
+    public void applyFromApplicationInfo(AppInfo info) {
+        LauncherAppState app = LauncherAppState.getInstance();
+        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
+
+        Drawable topDrawable = Utilities.createIconDrawable(info.iconBitmap);
+        topDrawable.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
+        setCompoundDrawables(null, topDrawable, null, null);
+        setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
+        setText(info.title);
+        if (info.contentDescription != null) {
+            setContentDescription(info.contentDescription);
+        }
+        setTag(info);
+    }
+
+
     @Override
     protected boolean setFrame(int left, int top, int right, int bottom) {
         if (getLeft() != left || getRight() != right || getTop() != top || getBottom() != bottom) {
@@ -169,98 +175,22 @@
             LauncherModel.checkItemInfo((ItemInfo) tag);
         }
         super.setTag(tag);
-        if (tag instanceof ShortcutInfo) {
-            final ShortcutInfo info = (ShortcutInfo) tag;
-            mDefaultText = info.title;
-            setText(mDefaultText);
-        }
     }
 
     @Override
-    protected void drawableStateChanged() {
-        if (isPressed()) {
-            // In this case, we have already created the pressed outline on ACTION_DOWN,
-            // so we just need to do an invalidate to trigger draw
-            if (!mDidInvalidateForPressedState) {
-                setCellLayoutPressedOrFocusedIcon();
-            }
-        } else {
-            // Otherwise, either clear the pressed/focused background, or create a background
-            // for the focused state
-            final boolean backgroundEmptyBefore = mPressedOrFocusedBackground == null;
-            if (!mStayPressed) {
-                mPressedOrFocusedBackground = null;
-            }
-            if (isFocused()) {
-                if (getLayout() == null) {
-                    // In some cases, we get focus before we have been layed out. Set the
-                    // background to null so that it will get created when the view is drawn.
-                    mPressedOrFocusedBackground = null;
-                } else {
-                    mPressedOrFocusedBackground = createGlowingOutline(
-                            mTempCanvas, mFocusedGlowColor, mFocusedOutlineColor);
-                }
-                mStayPressed = false;
-                setCellLayoutPressedOrFocusedIcon();
-            }
-            final boolean backgroundEmptyNow = mPressedOrFocusedBackground == null;
-            if (!backgroundEmptyBefore && backgroundEmptyNow) {
-                setCellLayoutPressedOrFocusedIcon();
-            }
+    public void setPressed(boolean pressed) {
+        super.setPressed(pressed);
+
+        if (!mIgnorePressedStateChange) {
+            updateIconState();
         }
+    }
 
-        Drawable d = mBackground;
-        if (d != null && d.isStateful()) {
-            d.setState(getDrawableState());
+    private void updateIconState() {
+        Drawable top = getCompoundDrawables()[1];
+        if (top instanceof FastBitmapDrawable) {
+            ((FastBitmapDrawable) top).setPressed(isPressed() || mStayPressed);
         }
-        super.drawableStateChanged();
-    }
-
-    /**
-     * Draw this BubbleTextView into the given Canvas.
-     *
-     * @param destCanvas the canvas to draw on
-     * @param padding the horizontal and vertical padding to use when drawing
-     */
-    private void drawWithPadding(Canvas destCanvas, int padding) {
-        final Rect clipRect = mTempRect;
-        getDrawingRect(clipRect);
-
-        // adjust the clip rect so that we don't include the text label
-        clipRect.bottom =
-            getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V + getLayout().getLineTop(0);
-
-        // Draw the View into the bitmap.
-        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
-        // they set scrollX and scrollY to large values to achieve centered text
-        destCanvas.save();
-        destCanvas.scale(getScaleX(), getScaleY(),
-                (getWidth() + padding) / 2, (getHeight() + padding) / 2);
-        destCanvas.translate(-getScrollX() + padding / 2, -getScrollY() + padding / 2);
-        destCanvas.clipRect(clipRect, Op.REPLACE);
-        draw(destCanvas);
-        destCanvas.restore();
-    }
-
-    public void setGlowColor(int color) {
-        mFocusedOutlineColor = mFocusedGlowColor = mPressedOutlineColor = mPressedGlowColor = color;
-    }
-
-    /**
-     * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
-     * Responsibility for the bitmap is transferred to the caller.
-     */
-    private Bitmap createGlowingOutline(Canvas canvas, int outlineColor, int glowColor) {
-        final int padding = mOutlineHelper.mMaxOuterBlurRadius;
-        final Bitmap b = Bitmap.createBitmap(
-                getWidth() + padding, getHeight() + padding, Bitmap.Config.ARGB_8888);
-
-        canvas.setBitmap(b);
-        drawWithPadding(canvas, padding);
-        mOutlineHelper.applyExtraThickExpensiveOutlineWithBlur(b, canvas, glowColor, outlineColor);
-        canvas.setBitmap(null);
-
-        return b;
     }
 
     @Override
@@ -271,20 +201,11 @@
 
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
-                // So that the pressed outline is visible immediately when isPressed() is true,
+                // So that the pressed outline is visible immediately on setStayPressed(),
                 // we pre-create it on ACTION_DOWN (it takes a small but perceptible amount of time
                 // to create it)
-                if (mPressedOrFocusedBackground == null) {
-                    mPressedOrFocusedBackground = createGlowingOutline(
-                            mTempCanvas, mPressedGlowColor, mPressedOutlineColor);
-                }
-                // Invalidate so the pressed state is visible, or set a flag so we know that we
-                // have to call invalidate as soon as the state is "pressed"
-                if (isPressed()) {
-                    mDidInvalidateForPressedState = true;
-                    setCellLayoutPressedOrFocusedIcon();
-                } else {
-                    mDidInvalidateForPressedState = false;
+                if (mPressedBackground == null) {
+                    mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
                 }
 
                 mLongPressHelper.postCheckForLongPress();
@@ -294,7 +215,7 @@
                 // If we've touched down and up on an item, and it's still not "pressed", then
                 // destroy the pressed outline
                 if (!isPressed()) {
-                    mPressedOrFocusedBackground = null;
+                    mPressedBackground = null;
                 }
 
                 mLongPressHelper.cancelLongPress();
@@ -311,34 +232,47 @@
     void setStayPressed(boolean stayPressed) {
         mStayPressed = stayPressed;
         if (!stayPressed) {
-            mPressedOrFocusedBackground = null;
+            mPressedBackground = null;
         }
-        setCellLayoutPressedOrFocusedIcon();
+
+        // Only show the shadow effect when persistent pressed state is set.
+        if (getParent() instanceof ShortcutAndWidgetContainer) {
+            CellLayout layout = (CellLayout) getParent().getParent();
+            layout.setPressedIcon(this, mPressedBackground, mOutlineHelper.shadowBitmapPadding);
+        }
+
+        updateIconState();
     }
 
-    void setCellLayoutPressedOrFocusedIcon() {
-        // Disable pressed state when the icon is in preloader state.
-        if ((getParent() instanceof ShortcutAndWidgetContainer) &&
-                !(getCompoundDrawables()[1] instanceof PreloadIconDrawable)){
-            ShortcutAndWidgetContainer parent = (ShortcutAndWidgetContainer) getParent();
-            if (parent != null) {
-                CellLayout layout = (CellLayout) parent.getParent();
-                layout.setPressedOrFocusedIcon((mPressedOrFocusedBackground != null) ? this : null);
+    void clearPressedBackground() {
+        setPressed(false);
+        setStayPressed(false);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (super.onKeyDown(keyCode, event)) {
+            // Pre-create shadow so show immediately on click.
+            if (mPressedBackground == null) {
+                mPressedBackground = mOutlineHelper.createMediumDropShadow(this);
             }
+            return true;
         }
+        return false;
     }
 
-    void clearPressedOrFocusedBackground() {
-        mPressedOrFocusedBackground = null;
-        setCellLayoutPressedOrFocusedIcon();
-    }
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        // Unlike touch events, keypress event propagate pressed state change immediately,
+        // without waiting for onClickHandler to execute. Disable pressed state changes here
+        // to avoid flickering.
+        mIgnorePressedStateChange = true;
+        boolean result = super.onKeyUp(keyCode, event);
 
-    Bitmap getPressedOrFocusedBackground() {
-        return mPressedOrFocusedBackground;
-    }
-
-    int getPressedOrFocusedBackgroundPadding() {
-        return mOutlineHelper.mMaxOuterBlurRadius / 2;
+        mPressedBackground = null;
+        mIgnorePressedStateChange = false;
+        updateIconState();
+        return result;
     }
 
     @Override
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 93006b3..b55b911 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -122,7 +122,7 @@
     private int mDragOutlineCurrent = 0;
     private final Paint mDragOutlinePaint = new Paint();
 
-    private BubbleTextView mPressedOrFocusedIcon;
+    private final FastBitmapView mTouchFeedbackView;
 
     private HashMap<CellLayout.LayoutParams, Animator> mReorderAnimators = new
             HashMap<CellLayout.LayoutParams, Animator>();
@@ -287,6 +287,9 @@
         mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mWidthGap, mHeightGap,
                 mCountX, mCountY);
 
+        mTouchFeedbackView = new FastBitmapView(context);
+        // Make the feedback view large enough to hold the blur bitmap.
+        addView(mTouchFeedbackView, (int) (grid.cellWidthPx * 1.5), (int) (grid.cellHeightPx * 1.5));
         addView(mShortcutsAndWidgets);
     }
 
@@ -333,14 +336,6 @@
         return mDropPending;
     }
 
-    private void invalidateBubbleTextView(BubbleTextView icon) {
-        final int padding = icon.getPressedOrFocusedBackgroundPadding();
-        invalidate(icon.getLeft() + getPaddingLeft() - padding,
-                icon.getTop() + getPaddingTop() - padding,
-                icon.getRight() + getPaddingLeft() + padding,
-                icon.getBottom() + getPaddingTop() + padding);
-    }
-
     void setOverScrollAmount(float r, boolean left) {
         if (left && mOverScrollForegroundDrawable != mOverScrollLeft) {
             mOverScrollForegroundDrawable = mOverScrollLeft;
@@ -354,16 +349,23 @@
         invalidate();
     }
 
-    void setPressedOrFocusedIcon(BubbleTextView icon) {
-        // We draw the pressed or focused BubbleTextView's background in CellLayout because it
-        // requires an expanded clip rect (due to the glow's blur radius)
-        BubbleTextView oldIcon = mPressedOrFocusedIcon;
-        mPressedOrFocusedIcon = icon;
-        if (oldIcon != null) {
-            invalidateBubbleTextView(oldIcon);
-        }
-        if (mPressedOrFocusedIcon != null) {
-            invalidateBubbleTextView(mPressedOrFocusedIcon);
+    void setPressedIcon(BubbleTextView icon, Bitmap background, int padding) {
+        if (icon == null || background == null) {
+            mTouchFeedbackView.setBitmap(null);
+            mTouchFeedbackView.animate().cancel();
+        } else {
+            int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight()
+                    - (mCountX * mCellWidth);
+            mTouchFeedbackView.setTranslationX(icon.getLeft() + (int) Math.ceil(offset / 2f)
+                    - padding);
+            mTouchFeedbackView.setTranslationY(icon.getTop() - padding);
+            if (mTouchFeedbackView.setBitmap(background)) {
+                mTouchFeedbackView.setAlpha(0);
+                mTouchFeedbackView.animate().alpha(1)
+                    .setDuration(FastBitmapDrawable.CLICK_FEEDBACK_DURATION)
+                    .setInterpolator(FastBitmapDrawable.CLICK_FEEDBACK_INTERPOLATOR)
+                    .start();
+            }
         }
     }
 
@@ -422,23 +424,6 @@
             }
         }
 
-        // We draw the pressed or focused BubbleTextView's background in CellLayout because it
-        // requires an expanded clip rect (due to the glow's blur radius)
-        if (mPressedOrFocusedIcon != null) {
-            final int padding = mPressedOrFocusedIcon.getPressedOrFocusedBackgroundPadding();
-            final Bitmap b = mPressedOrFocusedIcon.getPressedOrFocusedBackground();
-            if (b != null) {
-                int offset = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() -
-                        (mCountX * mCellWidth);
-                int left = getPaddingLeft() + (int) Math.ceil(offset / 2f);
-                int top = getPaddingTop();
-                canvas.drawBitmap(b,
-                        mPressedOrFocusedIcon.getLeft() + left - padding,
-                        mPressedOrFocusedIcon.getTop() + top - padding,
-                        null);
-            }
-        }
-
         if (DEBUG_VISUALIZE_OCCUPIED) {
             int[] pt = new int[2];
             ColorDrawable cd = new ColorDrawable(Color.RED);
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index cf7c22e..7c9e77e 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -16,30 +16,62 @@
 
 package com.android.launcher3;
 
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.ColorFilter;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.util.SparseArray;
 
 class FastBitmapDrawable extends Drawable {
 
+    static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() {
+
+        @Override
+        public float getInterpolation(float input) {
+            if (input < 0.05f) {
+                return input / 0.05f;
+            } else if (input < 0.3f){
+                return 1;
+            } else {
+                return (1 - input) / 0.7f;
+            }
+        }
+    };
+    static final long CLICK_FEEDBACK_DURATION = 2000;
+
+    private static final int PRESSED_BRIGHTNESS = 100;
     private static ColorMatrix sGhostModeMatrix;
     private static final ColorMatrix sTempMatrix = new ColorMatrix();
 
+    /**
+     * Store the brightness colors filters to optimize animations during icon press. This
+     * only works for non-ghost-mode icons.
+     */
+    private static final SparseArray<ColorFilter> sCachedBrightnessFilter =
+            new SparseArray<ColorFilter>();
+
     private static final int GHOST_MODE_MIN_COLOR_RANGE = 130;
 
     private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
-    private Bitmap mBitmap;
+    private final Bitmap mBitmap;
     private int mAlpha;
 
     private int mBrightness = 0;
     private boolean mGhostModeEnabled = false;
 
+    private boolean mPressed = false;
+    private ObjectAnimator mPressedAnimator;
+
     FastBitmapDrawable(Bitmap b) {
         mAlpha = 255;
         mBitmap = b;
@@ -114,6 +146,23 @@
         }
     }
 
+    public void setPressed(boolean pressed) {
+        if (mPressed != pressed) {
+            mPressed = pressed;
+            if (mPressed) {
+                mPressedAnimator = ObjectAnimator
+                        .ofInt(this, "brightness", PRESSED_BRIGHTNESS)
+                        .setDuration(CLICK_FEEDBACK_DURATION);
+                mPressedAnimator.setInterpolator(CLICK_FEEDBACK_INTERPOLATOR);
+                mPressedAnimator.start();
+            } else if (mPressedAnimator != null) {
+                mPressedAnimator.cancel();
+                setBrightness(0);
+            }
+        }
+        invalidateSelf();
+    }
+
     public boolean isGhostModeEnabled() {
         return mGhostModeEnabled;
     }
@@ -122,14 +171,11 @@
         return mBrightness;
     }
 
-    public void addBrightness(int amount) {
-        setBrightness(mBrightness + amount);
-    }
-
     public void setBrightness(int brightness) {
         if (mBrightness != brightness) {
             mBrightness = brightness;
             updateFilter();
+            invalidateSelf();
         }
     }
 
@@ -157,8 +203,13 @@
                 mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
             }
         } else if (mBrightness != 0) {
-            setBrightnessMatrix(sTempMatrix, mBrightness);
-            mPaint.setColorFilter(new ColorMatrixColorFilter(sTempMatrix));
+            ColorFilter filter = sCachedBrightnessFilter.get(mBrightness);
+            if (filter == null) {
+                filter = new PorterDuffColorFilter(Color.argb(mBrightness, 255, 255, 255),
+                        PorterDuff.Mode.SRC_ATOP);
+                sCachedBrightnessFilter.put(mBrightness, filter);
+            }
+            mPaint.setColorFilter(filter);
         } else {
             mPaint.setColorFilter(null);
         }
diff --git a/src/com/android/launcher3/FastBitmapView.java b/src/com/android/launcher3/FastBitmapView.java
new file mode 100644
index 0000000..0937eb7
--- /dev/null
+++ b/src/com/android/launcher3/FastBitmapView.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.view.View;
+
+public class FastBitmapView extends View {
+
+    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
+    private Bitmap mBitmap;
+
+    public FastBitmapView(Context context) {
+        super(context);
+    }
+
+    /**
+     * Applies the new bitmap.
+     * @return true if the view was invalidated.
+     */
+    public boolean setBitmap(Bitmap b) {
+        if (b != mBitmap){
+            if (mBitmap != null) {
+                invalidate(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+            }
+            mBitmap = b;
+            if (mBitmap != null) {
+                invalidate(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        if (mBitmap != null) {
+            canvas.drawBitmap(mBitmap, 0, 0, mPaint);
+        }
+    }
+}
diff --git a/src/com/android/launcher3/FocusHelper.java b/src/com/android/launcher3/FocusHelper.java
index df5e0fc..34e752b 100644
--- a/src/com/android/launcher3/FocusHelper.java
+++ b/src/com/android/launcher3/FocusHelper.java
@@ -22,8 +22,6 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.widget.ScrollView;
-import android.widget.TabHost;
-import android.widget.TabWidget;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -89,7 +87,6 @@
 
         final PagedViewGridLayout parent = (PagedViewGridLayout) w.getParent();
         final PagedView container = (PagedView) parent.getParent();
-        final AppsCustomizeTabHost tabHost = findTabHostParent(container);
         final int widgetIndex = parent.indexOfChild(w);
         final int widgetCount = parent.getChildCount();
         final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parent));
@@ -228,6 +225,13 @@
      * Handles key events in a PageViewCellLayout containing PagedViewIcons.
      */
     static boolean handleAppsCustomizeKeyEvent(View v, int keyCode, KeyEvent e) {
+        final int action = e.getAction();
+        if (((action == KeyEvent.ACTION_DOWN) && v.onKeyDown(keyCode, e))
+                || ((action == KeyEvent.ACTION_UP) && v.onKeyUp(keyCode, e))) {
+            // Let the view handle the confirmation key.
+            return true;
+        }
+
         ViewGroup parentLayout;
         ViewGroup itemContainer;
         int countX;
@@ -246,7 +250,6 @@
         // Note we have an extra parent because of the
         // PagedViewCellLayout/PagedViewCellLayoutChildren relationship
         final PagedView container = (PagedView) parentLayout.getParent();
-        final AppsCustomizeTabHost tabHost = findTabHostParent(container);
         final int iconIndex = itemContainer.indexOfChild(v);
         final int itemCount = itemContainer.getChildCount();
         final int pageIndex = ((PagedView) container).indexToPage(container.indexOfChild(parentLayout));
@@ -255,7 +258,6 @@
         final int x = iconIndex % countX;
         final int y = iconIndex / countX;
 
-        final int action = e.getAction();
         final boolean handleKeyEvent = (action != KeyEvent.ACTION_UP);
         ViewGroup newParent = null;
         // Side pages do not always load synchronously, so check before focusing child siblings
@@ -319,15 +321,6 @@
                 }
                 wasHandled = true;
                 break;
-            case KeyEvent.KEYCODE_ENTER:
-            case KeyEvent.KEYCODE_DPAD_CENTER:
-                if (handleKeyEvent) {
-                    // Simulate a click on the icon
-                    View.OnClickListener clickListener = (View.OnClickListener) container;
-                    clickListener.onClick(v);
-                }
-                wasHandled = true;
-                break;
             case KeyEvent.KEYCODE_PAGE_UP:
                 if (handleKeyEvent) {
                     // Select the first icon on the previous page, or the first icon on this page
diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java
index c548a6f..f26f87c 100644
--- a/src/com/android/launcher3/Folder.java
+++ b/src/com/android/launcher3/Folder.java
@@ -118,10 +118,6 @@
 
     private FocusIndicatorView mFocusIndicatorHandler;
 
-    private int DRAG_MODE_NONE = 0;
-    private int DRAG_MODE_REORDER = 1;
-    private int mDragMode = DRAG_MODE_NONE;
-
     // We avoid measuring the scroll view with a 0 width or height, as this
     // results in CellLayout being measured as UNSPECIFIED, which it does
     // not support.
@@ -254,7 +250,6 @@
 
             mLauncher.getLauncherClings().dismissFolderCling(null);
 
-            mLauncher.getWorkspace().onDragStartedWithItem(v);
             mLauncher.getWorkspace().beginDragShared(v, this);
 
             mCurrentDragInfo = item;
@@ -783,9 +778,6 @@
                 mReorderAlarm.setAlarm(REORDER_DELAY);
                 mPreviousTargetCell[0] = mTargetCell[0];
                 mPreviousTargetCell[1] = mTargetCell[1];
-                mDragMode = DRAG_MODE_REORDER;
-            } else {
-                mDragMode = DRAG_MODE_NONE;
             }
         }
     }
@@ -841,7 +833,6 @@
             mOnExitAlarm.setAlarm(ON_EXIT_CLOSE_DELAY);
         }
         mReorderAlarm.cancelAlarm();
-        mDragMode = DRAG_MODE_NONE;
     }
 
     public void onDropCompleted(final View target, final DragObject d,
diff --git a/src/com/android/launcher3/FolderIcon.java b/src/com/android/launcher3/FolderIcon.java
index 21efd71..a359f11 100644
--- a/src/com/android/launcher3/FolderIcon.java
+++ b/src/com/android/launcher3/FolderIcon.java
@@ -583,9 +583,10 @@
             d.setBounds(0, 0, mIntrinsicIconSize, mIntrinsicIconSize);
             if (d instanceof FastBitmapDrawable) {
                 FastBitmapDrawable fd = (FastBitmapDrawable) d;
-                fd.addBrightness(params.overlayAlpha);
+                int oldBrightness = fd.getBrightness();
+                fd.setBrightness(params.overlayAlpha);
                 d.draw(canvas);
-                fd.addBrightness(-params.overlayAlpha);
+                fd.setBrightness(oldBrightness);
             } else {
                 d.setColorFilter(Color.argb(params.overlayAlpha, 255, 255, 255),
                         PorterDuff.Mode.SRC_ATOP);
diff --git a/src/com/android/launcher3/HolographicOutlineHelper.java b/src/com/android/launcher3/HolographicOutlineHelper.java
index d7b960a..b1e0e68 100644
--- a/src/com/android/launcher3/HolographicOutlineHelper.java
+++ b/src/com/android/launcher3/HolographicOutlineHelper.java
@@ -20,48 +20,49 @@
 import android.graphics.Bitmap;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.Rect;
+import android.graphics.Region.Op;
 
 public class HolographicOutlineHelper {
-    private final Paint mHolographicPaint = new Paint();
+
+    private static final Rect sTempRect = new Rect();
+
+    private final Canvas mCanvas = new Canvas();
+    private final Paint mDrawPaint = new Paint();
     private final Paint mBlurPaint = new Paint();
     private final Paint mErasePaint = new Paint();
 
-    public int mMaxOuterBlurRadius;
-    public int mMinOuterBlurRadius;
+    private final BlurMaskFilter mMediumOuterBlurMaskFilter;
+    private final BlurMaskFilter mThinOuterBlurMaskFilter;
+    private final BlurMaskFilter mMediumInnerBlurMaskFilter;
 
-    private BlurMaskFilter mExtraThickOuterBlurMaskFilter;
-    private BlurMaskFilter mThickOuterBlurMaskFilter;
-    private BlurMaskFilter mMediumOuterBlurMaskFilter;
-    private BlurMaskFilter mThinOuterBlurMaskFilter;
-    private BlurMaskFilter mThickInnerBlurMaskFilter;
-    private BlurMaskFilter mExtraThickInnerBlurMaskFilter;
-    private BlurMaskFilter mMediumInnerBlurMaskFilter;
+    private final BlurMaskFilter mShaowBlurMaskFilter;
+    private final int mShadowOffset;
 
-    private static final int THICK = 0;
-    private static final int MEDIUM = 1;
-    private static final int EXTRA_THICK = 2;
+    /**
+     * Padding used when creating shadow bitmap;
+     */
+    final int shadowBitmapPadding;
 
     static HolographicOutlineHelper INSTANCE;
 
     private HolographicOutlineHelper(Context context) {
         final float scale = LauncherAppState.getInstance().getScreenDensity();
 
-        mMinOuterBlurRadius = (int) (scale * 1.0f);
-        mMaxOuterBlurRadius = (int) (scale * 12.0f);
-
-        mExtraThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 12.0f, BlurMaskFilter.Blur.OUTER);
-        mThickOuterBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.OUTER);
         mMediumOuterBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.OUTER);
         mThinOuterBlurMaskFilter = new BlurMaskFilter(scale * 1.0f, BlurMaskFilter.Blur.OUTER);
-        mExtraThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 6.0f, BlurMaskFilter.Blur.NORMAL);
-        mThickInnerBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
         mMediumInnerBlurMaskFilter = new BlurMaskFilter(scale * 2.0f, BlurMaskFilter.Blur.NORMAL);
 
-        mHolographicPaint.setFilterBitmap(true);
-        mHolographicPaint.setAntiAlias(true);
+        mShaowBlurMaskFilter = new BlurMaskFilter(scale * 4.0f, BlurMaskFilter.Blur.NORMAL);
+        mShadowOffset = (int) (scale * 2.0f);
+        shadowBitmapPadding = (int) (scale * 4.0f);
+
+        mDrawPaint.setFilterBitmap(true);
+        mDrawPaint.setAntiAlias(true);
         mBlurPaint.setFilterBitmap(true);
         mBlurPaint.setAntiAlias(true);
         mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
@@ -77,37 +78,15 @@
     }
 
     /**
-     * Returns the interpolated holographic highlight alpha for the effect we want when scrolling
-     * pages.
-     */
-    public static float highlightAlphaInterpolator(float r) {
-        float maxAlpha = 0.6f;
-        return (float) Math.pow(maxAlpha * (1.0f - r), 1.5f);
-    }
-
-    /**
-     * Returns the interpolated view alpha for the effect we want when scrolling pages.
-     */
-    public static float viewAlphaInterpolator(float r) {
-        final float pivot = 0.95f;
-        if (r < pivot) {
-            return (float) Math.pow(r / pivot, 1.5f);
-        } else {
-            return 1.0f;
-        }
-    }
-
-    /**
      * Applies a more expensive and accurate outline to whatever is currently drawn in a specified
      * bitmap.
      */
     void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor, int thickness) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true,
-                thickness);
+            int outlineColor) {
+        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, true);
     }
     void applyExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor, boolean clipAlpha, int thickness) {
+            int outlineColor, boolean clipAlpha) {
 
         // We start by removing most of the alpha channel so as to ignore shadows, and
         // other types of partial transparency when defining the shape of the object
@@ -127,50 +106,18 @@
         Bitmap glowShape = srcDst.extractAlpha();
 
         // calculate the outer blur first
-        BlurMaskFilter outerBlurMaskFilter;
-        switch (thickness) {
-            case EXTRA_THICK:
-                outerBlurMaskFilter = mExtraThickOuterBlurMaskFilter;
-                break;
-            case THICK:
-                outerBlurMaskFilter = mThickOuterBlurMaskFilter;
-                break;
-            case MEDIUM:
-                outerBlurMaskFilter = mMediumOuterBlurMaskFilter;
-                break;
-            default:
-                throw new RuntimeException("Invalid blur thickness");
-        }
-        mBlurPaint.setMaskFilter(outerBlurMaskFilter);
+        mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
         int[] outerBlurOffset = new int[2];
         Bitmap thickOuterBlur = glowShape.extractAlpha(mBlurPaint, outerBlurOffset);
-        if (thickness == EXTRA_THICK) {
-            mBlurPaint.setMaskFilter(mMediumOuterBlurMaskFilter);
-        } else {
-            mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
-        }
 
+        mBlurPaint.setMaskFilter(mThinOuterBlurMaskFilter);
         int[] brightOutlineOffset = new int[2];
         Bitmap brightOutline = glowShape.extractAlpha(mBlurPaint, brightOutlineOffset);
 
         // calculate the inner blur
         srcDstCanvas.setBitmap(glowShape);
         srcDstCanvas.drawColor(0xFF000000, PorterDuff.Mode.SRC_OUT);
-        BlurMaskFilter innerBlurMaskFilter;
-        switch (thickness) {
-            case EXTRA_THICK:
-                innerBlurMaskFilter = mExtraThickInnerBlurMaskFilter;
-                break;
-            case THICK:
-                innerBlurMaskFilter = mThickInnerBlurMaskFilter;
-                break;
-            case MEDIUM:
-                innerBlurMaskFilter = mMediumInnerBlurMaskFilter;
-                break;
-            default:
-                throw new RuntimeException("Invalid blur thickness");
-        }
-        mBlurPaint.setMaskFilter(innerBlurMaskFilter);
+        mBlurPaint.setMaskFilter(mMediumInnerBlurMaskFilter);
         int[] thickInnerBlurOffset = new int[2];
         Bitmap thickInnerBlur = glowShape.extractAlpha(mBlurPaint, thickInnerBlurOffset);
 
@@ -186,16 +133,16 @@
         // draw the inner and outer blur
         srcDstCanvas.setBitmap(srcDst);
         srcDstCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
-        mHolographicPaint.setColor(color);
+        mDrawPaint.setColor(color);
         srcDstCanvas.drawBitmap(thickInnerBlur, thickInnerBlurOffset[0], thickInnerBlurOffset[1],
-                mHolographicPaint);
+                mDrawPaint);
         srcDstCanvas.drawBitmap(thickOuterBlur, outerBlurOffset[0], outerBlurOffset[1],
-                mHolographicPaint);
+                mDrawPaint);
 
         // draw the bright outline
-        mHolographicPaint.setColor(outlineColor);
+        mDrawPaint.setColor(outlineColor);
         srcDstCanvas.drawBitmap(brightOutline, brightOutlineOffset[0], brightOutlineOffset[1],
-                mHolographicPaint);
+                mDrawPaint);
 
         // cleanup
         srcDstCanvas.setBitmap(null);
@@ -205,25 +152,52 @@
         glowShape.recycle();
     }
 
-    void applyExtraThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, EXTRA_THICK);
-    }
+    Bitmap createMediumDropShadow(BubbleTextView view) {
+        final Bitmap result = Bitmap.createBitmap(
+                view.getWidth() + shadowBitmapPadding + shadowBitmapPadding,
+                view.getHeight() + shadowBitmapPadding + shadowBitmapPadding + mShadowOffset,
+                Bitmap.Config.ARGB_8888);
 
-    void applyThickExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, THICK);
-    }
+        mCanvas.setBitmap(result);
 
-    void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor, boolean clipAlpha) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, clipAlpha,
-                MEDIUM);
-    }
+        final Rect clipRect = sTempRect;
+        view.getDrawingRect(sTempRect);
+        // adjust the clip rect so that we don't include the text label
+        clipRect.bottom = view.getExtendedPaddingTop() - (int) BubbleTextView.PADDING_V
+                + view.getLayout().getLineTop(0);
 
-    void applyMediumExpensiveOutlineWithBlur(Bitmap srcDst, Canvas srcDstCanvas, int color,
-            int outlineColor) {
-        applyExpensiveOutlineWithBlur(srcDst, srcDstCanvas, color, outlineColor, MEDIUM);
-    }
+        // Draw the View into the bitmap.
+        // The translate of scrollX and scrollY is necessary when drawing TextViews, because
+        // they set scrollX and scrollY to large values to achieve centered text
+        mCanvas.save();
+        mCanvas.scale(view.getScaleX(), view.getScaleY(),
+                view.getWidth() / 2 + shadowBitmapPadding,
+                view.getHeight() / 2 + shadowBitmapPadding);
+        mCanvas.translate(-view.getScrollX() + shadowBitmapPadding,
+                -view.getScrollY() + shadowBitmapPadding);
+        mCanvas.clipRect(clipRect, Op.REPLACE);
+        view.draw(mCanvas);
+        mCanvas.restore();
 
+        int[] blurOffst = new int[2];
+        mBlurPaint.setMaskFilter(mShaowBlurMaskFilter);
+        Bitmap blurBitmap = result.extractAlpha(mBlurPaint, blurOffst);
+
+        mCanvas.save();
+        mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
+        mCanvas.translate(blurOffst[0], blurOffst[1]);
+
+        mDrawPaint.setColor(Color.BLACK);
+        mDrawPaint.setAlpha(30);
+        mCanvas.drawBitmap(blurBitmap, 0, 0, mDrawPaint);
+
+        mDrawPaint.setAlpha(60);
+        mCanvas.drawBitmap(blurBitmap, 0, mShadowOffset, mDrawPaint);
+        mCanvas.restore();
+
+        mCanvas.setBitmap(null);
+        blurBitmap.recycle();
+
+        return result;
+    }
 }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 88a60d0..b857100 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -82,7 +82,6 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnLongClickListener;
-import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
@@ -1026,10 +1025,6 @@
             // Resets the previous workspace icon press state
             mWaitingForResume.setStayPressed(false);
         }
-        if (mAppsCustomizeContent != null) {
-            // Resets the previous all apps icon press state
-            mAppsCustomizeContent.resetDrawableState();
-        }
 
         // It is possible that widgets can receive updates while launcher is not in the foreground.
         // Consequently, the widgets will be inflated in the orientation of the foreground activity
@@ -2451,6 +2446,8 @@
             }
         } else if (v == mAllAppsButton) {
             onClickAllAppsButton(v);
+        } else if (tag instanceof AppInfo) {
+            startAppShortcutOrInfoActivity(v);
         } else if (tag instanceof LauncherAppWidgetInfo) {
             if (v instanceof PendingAppWidgetHostView) {
                 onClickPendingWidget((PendingAppWidgetHostView) v);
@@ -2458,6 +2455,10 @@
         }
     }
 
+    public void onClickPagedViewIcon(View v) {
+        startAppShortcutOrInfoActivity(v);
+    }
+
     public boolean onTouch(View v, MotionEvent event) {
         return false;
     }
@@ -2539,17 +2540,6 @@
     }
 
     /**
-     * Event handler for a paged view icon click.
-     * @param v The view that was clicked.
-     * @param appInfo The {link AppInfo} of the view.
-     */
-    public void onClickPagedViewIcon(View v, AppInfo appInfo) {
-        if (LOGD) Log.d(TAG, "onClickPagedViewIcon");
-        startActivitySafely(v, appInfo.intent, appInfo);
-        getStats().recordLaunch(appInfo.intent);
-    }
-
-    /**
      * Event handler for an app shortcut click.
      *
      * @param v The view that was clicked. Must be a tagged with a {@link ShortcutInfo}.
@@ -2586,7 +2576,7 @@
             builder.setPositiveButton(R.string.abandoned_search,
                     new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int id) {
-                            startAppShortcutActivity(v);
+                            startAppShortcutOrInfoActivity(v);
                         }
                     }
             );
@@ -2603,24 +2593,29 @@
         }
 
         // Start activities
-        startAppShortcutActivity(v);
+        startAppShortcutOrInfoActivity(v);
     }
 
-    private void startAppShortcutActivity(View v) {
+    private void startAppShortcutOrInfoActivity(View v) {
         Object tag = v.getTag();
-        if (!(tag instanceof ShortcutInfo)) {
-            throw new IllegalArgumentException("Input must be a Shortcut");
-        }
-        final ShortcutInfo shortcut = (ShortcutInfo) tag;
-        final Intent intent = shortcut.intent;
+        final ShortcutInfo shortcut;
+        final Intent intent;
+        if (tag instanceof ShortcutInfo) {
+            shortcut = (ShortcutInfo) tag;
+            intent = shortcut.intent;
+            int[] pos = new int[2];
+            v.getLocationOnScreen(pos);
+            intent.setSourceBounds(new Rect(pos[0], pos[1],
+                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
 
-        int[] pos = new int[2];
-        v.getLocationOnScreen(pos);
-        intent.setSourceBounds(new Rect(pos[0], pos[1],
-                pos[0] + v.getWidth(), pos[1] + v.getHeight()));
+        } else if (tag instanceof AppInfo) {
+            shortcut = null;
+            intent = ((AppInfo) tag).intent;
+        } else {
+            throw new IllegalArgumentException("Input must be a Shortcut or AppInfo");
+        }
 
         boolean success = startActivitySafely(v, intent, tag);
-
         mStats.recordLaunch(intent, shortcut);
 
         if (success && v instanceof BubbleTextView) {
diff --git a/src/com/android/launcher3/PagedViewIcon.java b/src/com/android/launcher3/PagedViewIcon.java
deleted file mode 100644
index e819d5e..0000000
--- a/src/com/android/launcher3/PagedViewIcon.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2010 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;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Region;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.widget.TextView;
-
-/**
- * An icon on a PagedView, specifically for items in the launcher's paged view (with compound
- * drawables on the top).
- */
-public class PagedViewIcon extends TextView {
-    /** A simple callback interface to allow a PagedViewIcon to notify when it has been pressed */
-    public static interface PressedCallback {
-        void iconPressed(PagedViewIcon icon);
-    }
-
-    @SuppressWarnings("unused")
-    private static final String TAG = "PagedViewIcon";
-    private static final float PRESS_ALPHA = 0.4f;
-
-    private PagedViewIcon.PressedCallback mPressedCallback;
-    private boolean mLockDrawableState = false;
-
-    private Bitmap mIcon;
-
-    public PagedViewIcon(Context context) {
-        this(context, null);
-    }
-
-    public PagedViewIcon(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public PagedViewIcon(Context context, AttributeSet attrs, int defStyle) {
-        super(context, attrs, defStyle);
-    }
-
-    public void onFinishInflate() {
-        super.onFinishInflate();
-
-        // Ensure we are using the right text size
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-        setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
-    }
-
-    public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
-            PagedViewIcon.PressedCallback cb) {
-        LauncherAppState app = LauncherAppState.getInstance();
-        DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
-
-        mIcon = info.iconBitmap;
-        mPressedCallback = cb;
-        Drawable icon = Utilities.createIconDrawable(mIcon);
-        icon.setBounds(0, 0, grid.allAppsIconSizePx, grid.allAppsIconSizePx);
-        setCompoundDrawables(null, icon, null, null);
-        setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
-        setText(info.title);
-        if (info.contentDescription != null) {
-            setContentDescription(info.contentDescription);
-        }
-        setTag(info);
-    }
-
-    public void lockDrawableState() {
-        mLockDrawableState = true;
-    }
-
-    public void resetDrawableState() {
-        mLockDrawableState = false;
-        post(new Runnable() {
-            @Override
-            public void run() {
-                refreshDrawableState();
-            }
-        });
-    }
-
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-
-        // We keep in the pressed state until resetDrawableState() is called to reset the press
-        // feedback
-        if (isPressed()) {
-            setAlpha(PRESS_ALPHA);
-            if (mPressedCallback != null) {
-                mPressedCallback.iconPressed(this);
-            }
-        } else if (!mLockDrawableState) {
-            setAlpha(1f);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 53a3f94..35c8589 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -27,7 +27,6 @@
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.app.WallpaperManager;
-import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
@@ -47,9 +46,7 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
-import android.os.Handler.Callback;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.Parcelable;
 import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
@@ -76,9 +73,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * The workspace is a wide area with a wallpaper and a finite number of pages.
@@ -212,7 +207,7 @@
 
     private HolographicOutlineHelper mOutlineHelper;
     private Bitmap mDragOutline = null;
-    private final Rect mTempRect = new Rect();
+    private static final Rect sTempRect = new Rect();
     private final int[] mTempXY = new int[2];
     private int[] mTempVisiblePagesRange = new int[2];
     private boolean mOverscrollEffectSet;
@@ -241,6 +236,8 @@
     private DropTarget.DragEnforcer mDragEnforcer;
     private float mMaxDistanceForFolderCreation;
 
+    private final Canvas mCanvas = new Canvas();
+
     // Variables relating to touch disambiguation (scrolling workspace vs. scrolling a widget)
     private float mXDown;
     private float mYDown;
@@ -1980,14 +1977,7 @@
     * appearance).
     *
     */
-    public void onDragStartedWithItem(View v) {
-        final Canvas canvas = new Canvas();
-
-        // The outline is used to visualize where the item will land if dropped
-        mDragOutline = createDragOutline(v, canvas, DRAG_BITMAP_PADDING);
-    }
-
-    private Rect getDrawableBounds(Drawable d) {
+    private static Rect getDrawableBounds(Drawable d) {
         Rect bounds = new Rect();
         d.copyBounds(bounds);
         if (bounds.width() == 0 || bounds.height() == 0) {
@@ -2003,8 +1993,6 @@
     }
 
     public void onExternalDragStartedWithItem(View v) {
-        final Canvas canvas = new Canvas();
-
         // Compose a drag bitmap with the view scaled to the icon size
         LauncherAppState app = LauncherAppState.getInstance();
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
@@ -2024,22 +2012,19 @@
         // Compose the bitmap to create the icon from
         Bitmap b = Bitmap.createBitmap(bmpWidth, bmpHeight,
                 Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(b);
-        drawDragView(v, c, 0);
-        c.setBitmap(null);
+        mCanvas.setBitmap(b);
+        drawDragView(v, mCanvas, 0);
+        mCanvas.setBitmap(null);
 
         // The outline is used to visualize where the item will land if dropped
-        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
+        mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, iconSize, iconSize, true);
     }
 
     public void onDragStartedWithItem(PendingAddItemInfo info, Bitmap b, boolean clipAlpha) {
-        final Canvas canvas = new Canvas();
-
         int[] size = estimateItemSize(info.spanX, info.spanY, info, false);
 
         // The outline is used to visualize where the item will land if dropped
-        mDragOutline = createDragOutline(b, canvas, DRAG_BITMAP_PADDING, size[0],
-                size[1], clipAlpha);
+        mDragOutline = createDragOutline(b, DRAG_BITMAP_PADDING, size[0], size[1], clipAlpha);
     }
 
     public void exitWidgetResizeMode() {
@@ -2537,8 +2522,8 @@
      * @param destCanvas the canvas to draw on
      * @param padding the horizontal and vertical padding to use when drawing
      */
-    private void drawDragView(View v, Canvas destCanvas, int padding) {
-        final Rect clipRect = mTempRect;
+    private static void drawDragView(View v, Canvas destCanvas, int padding) {
+        final Rect clipRect = sTempRect;
         v.getDrawingRect(clipRect);
 
         boolean textVisible = false;
@@ -2577,7 +2562,7 @@
      * @param expectedPadding padding to add to the drag view. If a different padding was used
      * its value will be changed
      */
-    public Bitmap createDragBitmap(View v, Canvas canvas, AtomicInteger expectedPadding) {
+    public Bitmap createDragBitmap(View v, AtomicInteger expectedPadding) {
         Bitmap b;
 
         int padding = expectedPadding.get();
@@ -2592,9 +2577,9 @@
                     v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
         }
 
-        canvas.setBitmap(b);
-        drawDragView(v, canvas, padding);
-        canvas.setBitmap(null);
+        mCanvas.setBitmap(b);
+        drawDragView(v, mCanvas, padding);
+        mCanvas.setBitmap(null);
 
         return b;
     }
@@ -2603,15 +2588,15 @@
      * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
      * Responsibility for the bitmap is transferred to the caller.
      */
-    private Bitmap createDragOutline(View v, Canvas canvas, int padding) {
+    private Bitmap createDragOutline(View v, int padding) {
         final int outlineColor = getResources().getColor(R.color.outline_color);
         final Bitmap b = Bitmap.createBitmap(
                 v.getWidth() + padding, v.getHeight() + padding, Bitmap.Config.ARGB_8888);
 
-        canvas.setBitmap(b);
-        drawDragView(v, canvas, padding);
-        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor);
-        canvas.setBitmap(null);
+        mCanvas.setBitmap(b);
+        drawDragView(v, mCanvas, padding);
+        mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor);
+        mCanvas.setBitmap(null);
         return b;
     }
 
@@ -2619,11 +2604,11 @@
      * Returns a new bitmap to be used as the object outline, e.g. to visualize the drop location.
      * Responsibility for the bitmap is transferred to the caller.
      */
-    private Bitmap createDragOutline(Bitmap orig, Canvas canvas, int padding, int w, int h,
+    private Bitmap createDragOutline(Bitmap orig, int padding, int w, int h,
             boolean clipAlpha) {
         final int outlineColor = getResources().getColor(R.color.outline_color);
         final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
-        canvas.setBitmap(b);
+        mCanvas.setBitmap(b);
 
         Rect src = new Rect(0, 0, orig.getWidth(), orig.getHeight());
         float scaleFactor = Math.min((w - padding) / (float) orig.getWidth(),
@@ -2635,10 +2620,10 @@
         // center the image
         dst.offset((w - scaledWidth) / 2, (h - scaledHeight) / 2);
 
-        canvas.drawBitmap(orig, src, dst, null);
-        mOutlineHelper.applyMediumExpensiveOutlineWithBlur(b, canvas, outlineColor, outlineColor,
+        mCanvas.drawBitmap(orig, src, dst, null);
+        mOutlineHelper.applyExpensiveOutlineWithBlur(b, mCanvas, outlineColor, outlineColor,
                 clipAlpha);
-        canvas.setBitmap(null);
+        mCanvas.setBitmap(null);
 
         return b;
     }
@@ -2656,21 +2641,20 @@
         CellLayout layout = (CellLayout) child.getParent().getParent();
         layout.prepareChildForDrag(child);
 
-        child.clearFocus();
-        child.setPressed(false);
-
-        final Canvas canvas = new Canvas();
-
-        // The outline is used to visualize where the item will land if dropped
-        mDragOutline = createDragOutline(child, canvas, DRAG_BITMAP_PADDING);
         beginDragShared(child, this);
     }
 
     public void beginDragShared(View child, DragSource source) {
+        child.clearFocus();
+        child.setPressed(false);
+
+        // The outline is used to visualize where the item will land if dropped
+        mDragOutline = createDragOutline(child, DRAG_BITMAP_PADDING);
+
         mLauncher.onDragStarted(child);
         // The drag bitmap follows the touch point around on the screen
         AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
-        final Bitmap b = createDragBitmap(child, new Canvas(), padding);
+        final Bitmap b = createDragBitmap(child, padding);
 
         final int bmpWidth = b.getWidth();
         final int bmpHeight = b.getHeight();
@@ -2684,7 +2668,7 @@
         DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
         Point dragVisualizeOffset = null;
         Rect dragRect = null;
-        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
+        if (child instanceof BubbleTextView) {
             int iconSize = grid.iconSizePx;
             int top = child.getPaddingTop();
             int left = (bmpWidth - iconSize) / 2;
@@ -2703,7 +2687,7 @@
         // Clear the pressed state if necessary
         if (child instanceof BubbleTextView) {
             BubbleTextView icon = (BubbleTextView) child;
-            icon.clearPressedOrFocusedBackground();
+            icon.clearPressedBackground();
         } else if (child instanceof FolderIcon) {
             // The folder cling isn't flexible enough to be shown in non-default workspace positions
             // Also if they are dragging it a folder, we assume they don't need to see the cling.
@@ -2738,14 +2722,14 @@
 
         // Compose a new drag bitmap that is of the icon size
         AtomicInteger padding = new AtomicInteger(DRAG_BITMAP_PADDING);
-        final Bitmap tmpB = createDragBitmap(child, new Canvas(), padding);
+        final Bitmap tmpB = createDragBitmap(child, padding);
         Bitmap b = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
         Paint p = new Paint();
         p.setFilterBitmap(true);
-        Canvas c = new Canvas(b);
-        c.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
+        mCanvas.setBitmap(b);
+        mCanvas.drawBitmap(tmpB, new Rect(0, 0, tmpB.getWidth(), tmpB.getHeight()),
                 new Rect(0, 0, iconSize, iconSize), p);
-        c.setBitmap(null);
+        mCanvas.setBitmap(null);
 
         // Find the child's location on the screen
         int bmpWidth = tmpB.getWidth();
@@ -4020,12 +4004,12 @@
         int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
         Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
                 Bitmap.Config.ARGB_8888);
-        Canvas c = new Canvas(b);
+        mCanvas.setBitmap(b);
 
         layout.measure(width, height);
         layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
-        layout.draw(c);
-        c.setBitmap(null);
+        layout.draw(mCanvas);
+        mCanvas.setBitmap(null);
         layout.setVisibility(visibility);
         return b;
     }