Caching icon labels to bitmaps for better performance

Change-Id: I78a3c116c1103b5b994a47f2cfcff18c0a9b31b8
diff --git a/src/com/android/launcher2/BubbleTextView.java b/src/com/android/launcher2/BubbleTextView.java
index f4a3d44..6039307 100644
--- a/src/com/android/launcher2/BubbleTextView.java
+++ b/src/com/android/launcher2/BubbleTextView.java
@@ -19,6 +19,7 @@
 import android.widget.TextView;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.RectF;
@@ -32,7 +33,7 @@
  * because we want to make the bubble taller than the text and TextView's clip is
  * too aggressive.
  */
-public class BubbleTextView extends TextView {
+public class BubbleTextView extends CacheableTextView {
     static final float CORNER_RADIUS = 8.0f;
     static final float PADDING_H = 5.0f;
     static final float PADDING_V = 1.0f;
@@ -77,6 +78,18 @@
         mPaddingV = PADDING_V * scale;
     }
 
+    public void applyFromShortcutInfo(ShortcutInfo info, IconCache iconCache) {
+        Bitmap b = info.getIcon(iconCache);
+
+        setCompoundDrawablesWithIntrinsicBounds(null,
+                new FastBitmapDrawable(b),
+                null, null);
+        setText(info.title);
+        buildAndEnableCache();
+        setTag(info);
+
+    }
+
     @Override
     protected boolean setFrame(int left, int top, int right, int bottom) {
         if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
@@ -129,7 +142,9 @@
                 top + layout.getLineTop(0) -  mPaddingV,
                 Math.min(left + layout.getLineRight(0) + mPaddingH, mScrollX + mRight - mLeft),
                 top + layout.getLineBottom(0) + mPaddingV);
-        canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, mPaint);
+        // TEMPORARILY DISABLE DRAWING ROUND RECT -- re-enable this when we tweak CacheableTextView
+        // to support padding so we can capture the "rounded" edges
+        //canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, mPaint);
 
         super.draw(canvas);
     }
diff --git a/src/com/android/launcher2/CacheableTextView.java b/src/com/android/launcher2/CacheableTextView.java
new file mode 100644
index 0000000..26eafa9
--- /dev/null
+++ b/src/com/android/launcher2/CacheableTextView.java
@@ -0,0 +1,122 @@
+/*
+ * 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.launcher2;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Bitmap.Config;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.widget.TextView;
+
+/*
+ * This class is a bit of a hack, designed to speed up long text labels in Launcher. It caches the
+ * text in a TextView to a bitmap and then just draws that Bitmap instead afterward, speeding up
+ * rendering. Marquee scrolling is not currently supported.
+ *
+ */
+public class CacheableTextView extends TextView {
+    private Bitmap mCache;
+    private final Paint mCachePaint = new Paint();
+
+    private int mPrevAlpha = -1;
+    private boolean mIsBuildingCache;
+    boolean mWaitingToGenerateCache;
+    float mTextCacheLeft;
+    float mTextCacheTop;
+    float mTextCacheScrollX;
+    float mRectLeft, mRectTop;
+    private float mPaddingH = 0;
+    private float mPaddingV = 0;
+
+    public CacheableTextView(Context context) {
+        super(context);
+    }
+
+    public CacheableTextView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CacheableTextView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public void buildAndEnableCache() {
+        if (getLayout() == null) {
+            mWaitingToGenerateCache = true;
+            return;
+        }
+
+        final Layout layout = getLayout();
+
+        final int left = getCompoundPaddingLeft();
+        final int top = getExtendedPaddingTop();
+        mTextCacheLeft = layout.getLineLeft(0);
+        mTextCacheTop = top + layout.getLineTop(0) - mPaddingV;
+
+        mRectLeft = mScrollX + getLeft();
+        mRectTop = 0;
+        mTextCacheScrollX = mScrollX;
+
+        final float textCacheRight =
+            Math.min(left + layout.getLineRight(0) + mPaddingH, mScrollX + mRight - mLeft);
+        final float textCacheBottom = top + layout.getLineBottom(0) + mPaddingV;
+
+        mCache = Bitmap.createBitmap((int) (textCacheRight - mTextCacheLeft),
+                (int) (textCacheBottom - mTextCacheTop), Config.ARGB_8888);
+        Canvas c = new Canvas(mCache);
+        c.translate(-mTextCacheLeft, -mTextCacheTop);
+
+        mIsBuildingCache = true;
+        float alpha = getAlpha();
+        setAlpha(1.0f);
+        draw(c);
+        setAlpha(alpha);
+        mIsBuildingCache = false;
+        mCachePaint.setFilterBitmap(true);
+
+        // A hack-- we set the text to be one space (we don't make it empty just to avoid any
+        // potential issues with text measurement, like line height, etc.) so that the text view
+        // doesn't draw it anymore, since it's been cached. We have to manually rebuild
+        // the cache whenever the text is changed (which is never in Launcher)
+        setText(" ");
+    }
+
+    public void draw(Canvas canvas) {
+        if (mWaitingToGenerateCache && !mIsBuildingCache) {
+            buildAndEnableCache();
+            mWaitingToGenerateCache = false;
+        }
+        if (mCache != null) {
+            canvas.drawBitmap(mCache, mTextCacheLeft - mTextCacheScrollX + mScrollX,
+                    mTextCacheTop, mCachePaint);
+        }
+        super.draw(canvas);
+    }
+
+    @Override
+    protected boolean onSetAlpha(int alpha) {
+        if (mPrevAlpha != alpha) {
+            mPrevAlpha = alpha;
+            mCachePaint.setAlpha(alpha);
+            super.onSetAlpha(alpha);
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher2/Launcher.java b/src/com/android/launcher2/Launcher.java
index 033ccc3..eacaef9 100644
--- a/src/com/android/launcher2/Launcher.java
+++ b/src/com/android/launcher2/Launcher.java
@@ -1038,17 +1038,9 @@
      * @return A View inflated from layoutResId.
      */
     View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
-        TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);
-
-        Bitmap b = info.getIcon(mIconCache);
-
-        favorite.setCompoundDrawablesWithIntrinsicBounds(null,
-                new FastBitmapDrawable(b),
-                null, null);
-        favorite.setText(info.title);
-        favorite.setTag(info);
+        BubbleTextView favorite = (BubbleTextView) mInflater.inflate(layoutResId, parent, false);
+        favorite.applyFromShortcutInfo(info, mIconCache);
         favorite.setOnClickListener(this);
-
         return favorite;
     }
 
diff --git a/src/com/android/launcher2/PagedViewIcon.java b/src/com/android/launcher2/PagedViewIcon.java
index b9b9b37..9e48351 100644
--- a/src/com/android/launcher2/PagedViewIcon.java
+++ b/src/com/android/launcher2/PagedViewIcon.java
@@ -39,7 +39,7 @@
  * 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 implements Checkable {
+public class PagedViewIcon extends CacheableTextView implements Checkable {
     private static final String TAG = "PagedViewIcon";
 
     // holographic outline
@@ -138,6 +138,7 @@
         mIcon = info.iconBitmap;
         setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
         setText(info.title);
+        buildAndEnableCache();
         setTag(info);
 
         queueHolographicOutlineCreation();
@@ -153,6 +154,7 @@
                 modelIconCache.getFullResIcon(info, packageManager), mContext);
         setCompoundDrawablesWithIntrinsicBounds(null, new FastBitmapDrawable(mIcon), null, null);
         setText(info.loadLabel(packageManager));
+        buildAndEnableCache();
         setTag(info);
 
         queueHolographicOutlineCreation();