Add drop shadows.

Change-Id: Ic6a72409d4785968d1fbdff229f17ee5c00b240b
diff --git a/api/current.xml b/api/current.xml
index 4ec1c3e..d35037e 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -73993,7 +73993,7 @@
 <method name="setShadowLayer"
  return="void"
  abstract="false"
- native="true"
+ native="false"
  synchronized="false"
  static="false"
  final="false"
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 91dbe1f..4c72e95 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -756,6 +756,12 @@
     private boolean setupModifiers(Paint paint) {
         boolean hasModifier = false;
 
+        if (paint.hasShadow) {
+            nSetupShadow(mRenderer, paint.shadowRadius, paint.shadowDx, paint.shadowDy,
+                    paint.shadowColor);
+            hasModifier = true;
+        }
+
         final Shader shader = paint.getShader();
         if (shader != null) {
             nSetupShader(mRenderer, shader.native_shader);
@@ -770,7 +776,7 @@
 
         return hasModifier;
     }
-    
+
     private boolean setupColorFilter(Paint paint) {
         final ColorFilter filter = paint.getColorFilter();
         if (filter != null) {
@@ -782,5 +788,7 @@
     
     private native void nSetupShader(int renderer, int shader);
     private native void nSetupColorFilter(int renderer, int colorFilter);
+    private native void nSetupShadow(int renderer, float radius, float dx, float dy, int color);
+
     private native void nResetModifiers(int renderer);
 }
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index e4d4850..ca9f371 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -738,7 +738,7 @@
                                         (void*) SkPaintGlue::getStringBounds },
     {"nativeGetCharArrayBounds", "(I[CIILandroid/graphics/Rect;)V",
                                     (void*) SkPaintGlue::getCharArrayBounds },
-    {"setShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
+    {"nSetShadowLayer", "(FFFI)V", (void*)SkPaintGlue::setShadowLayer}
 };
 
 static jfieldID req_fieldID(jfieldID id) {
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 9f94af9..4c6eced 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -259,6 +259,7 @@
         OpenGLRenderer* renderer) {
     renderer->resetShader();
     renderer->resetColorFilter();
+    renderer->resetShadow();
 }
 
 static void android_view_GLES20Canvas_setupShader(JNIEnv* env, jobject canvas,
@@ -271,6 +272,11 @@
     renderer->setupColorFilter(filter);
 }
 
+static void android_view_GLES20Canvas_setupShadow(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, jfloat radius, jfloat dx, jfloat dy, jint color) {
+    renderer->setupShadow(radius, dx, dy, color);
+}
+
 // ----------------------------------------------------------------------------
 // Text
 // ----------------------------------------------------------------------------
@@ -402,6 +408,7 @@
     { "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
     { "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
     { "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
+    { "nSetupShadow",       "(IFFFI)V",        (void*) android_view_GLES20Canvas_setupShadow },
 
     { "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
     { "nDrawText",          "(ILjava/lang/String;IIFFII)V",
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 6349cb3..62fbfb4 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -43,6 +43,28 @@
     private boolean     mHasCompatScaling;
     private float       mCompatScaling;
     private float       mInvCompatScaling;
+
+    /**
+     * @hide
+     */
+    public boolean hasShadow;
+    /**
+     * @hide
+     */
+    public float shadowDx;
+    /**
+     * @hide
+     */
+    public float shadowDy;
+    /**
+     * @hide
+     */
+    public float shadowRadius;
+    /**
+     * @hide
+     */
+    public int shadowColor;
+
     /**
      * @hide
      */
@@ -935,13 +957,23 @@
      * offset and color, and blur radius. If radius is 0, then the shadow
      * layer is removed.
      */
-    public native void setShadowLayer(float radius, float dx, float dy, int color);
+    public void setShadowLayer(float radius, float dx, float dy, int color) {
+        hasShadow = radius > 0.0f;
+        shadowRadius = radius;
+        shadowDx = dx;
+        shadowDy = dy;
+        shadowColor = color;
+        nSetShadowLayer(radius, dx, dy, color);
+    }
+    
+    private native void nSetShadowLayer(float radius, float dx, float dy, int color);
 
     /**
      * Clear the shadow layer.
      */
     public void clearShadowLayer() {
-        setShadowLayer(0, 0, 0, 0);
+        hasShadow = false;
+        nSetShadowLayer(0, 0, 0, 0);
     }
 
     /**
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 1efe6b5..8ed3d7b 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -17,7 +17,8 @@
 		ProgramCache.cpp \
 		SkiaColorFilter.cpp \
 		SkiaShader.cpp \
-		TextureCache.cpp
+		TextureCache.cpp \
+		TextDropShadowCache.cpp
 	
 	LOCAL_C_INCLUDES += \
 		$(JNI_H_INCLUDE) \
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index e807aba..ccc92eb 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -128,8 +128,11 @@
 }
 
 Font::CachedGlyphInfo* Font::getCachedUTFChar(SkPaint* paint, int32_t utfChar) {
-    CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(utfChar);
-    if (cachedGlyph == NULL) {
+    CachedGlyphInfo* cachedGlyph = NULL;
+    ssize_t index = mCachedGlyphs.indexOfKey(utfChar);
+    if (index >= 0) {
+        cachedGlyph = mCachedGlyphs.valueAt(index);
+    } else {
         cachedGlyph = cacheGlyph(paint, utfChar);
     }
 
@@ -510,10 +513,10 @@
             uint32_t yOffset = cl->mCurrentRow;
             uint32_t width   = mCacheWidth;
             uint32_t height  = cl->mMaxHeight;
-            void*    textureData = mTextTexture + yOffset*width;
+            void* textureData = mTextTexture + yOffset*width;
 
             glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, width, height,
-                             GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
+                    GL_ALPHA, GL_UNSIGNED_BYTE, textureData);
 
             cl->mDirty = false;
         }
@@ -617,21 +620,33 @@
     }
 }
 FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const char *text,
-                                uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+        uint32_t startIndex, uint32_t len, int numGlyphs, uint32_t radius) {
+    checkInit();
+
+    if (!mCurrentFont) {
+        DropShadow image;
+        image.width = 0;
+        image.height = 0;
+        image.image = NULL;
+        image.penX = 0;
+        image.penY = 0;
+        return image;
+    }
 
     Rect bounds;
     mCurrentFont->measureUTF(paint, text, startIndex, len, numGlyphs, &bounds);
-    uint32_t paddedWidth = (uint32_t)(bounds.right - bounds.left) + 2*radius;
-    uint32_t paddedHeight = (uint32_t)(bounds.top - bounds.bottom) + 2*radius;
+    uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * radius;
+    uint32_t paddedHeight = (uint32_t) (bounds.top - bounds.bottom) + 2 * radius;
     uint8_t* dataBuffer = new uint8_t[paddedWidth * paddedHeight];
-    for(uint32_t i = 0; i < paddedWidth * paddedHeight; i ++) {
+    for (uint32_t i = 0; i < paddedWidth * paddedHeight; i++) {
         dataBuffer[i] = 0;
     }
+
     int penX = radius - bounds.left;
     int penY = radius - bounds.bottom;
 
     mCurrentFont->renderUTF(paint, text, startIndex, len, numGlyphs, penX, penY,
-                              dataBuffer, paddedWidth, paddedHeight);
+            dataBuffer, paddedWidth, paddedHeight);
     blurImage(dataBuffer, paddedWidth, paddedHeight, radius);
 
     DropShadow image;
@@ -699,8 +714,7 @@
 }
 
 void FontRenderer::horizontalBlur(float* weights, int32_t radius,
-                                    const uint8_t* source, uint8_t* dest,
-                                    int32_t width, int32_t height) {
+        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
     float blurredPixel = 0.0f;
     float currentPixel = 0.0f;
 
@@ -744,8 +758,7 @@
 }
 
 void FontRenderer::verticalBlur(float* weights, int32_t radius,
-                                  const uint8_t* source, uint8_t* dest,
-                                  int32_t width, int32_t height) {
+        const uint8_t* source, uint8_t* dest, int32_t width, int32_t height) {
     float blurredPixel = 0.0f;
     float currentPixel = 0.0f;
 
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 6346ded..96c92d5 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -129,6 +129,14 @@
             uint32_t len, int numGlyphs, int x, int y);
 
     struct DropShadow {
+        DropShadow() { };
+
+        DropShadow(const DropShadow& dropShadow):
+            width(dropShadow.width), height(dropShadow.height),
+            image(dropShadow.image), penX(dropShadow.penX),
+            penY(dropShadow.penY) {
+        }
+
         uint32_t width;
         uint32_t height;
         uint8_t* image;
@@ -139,7 +147,7 @@
     // After renderDropShadow returns, the called owns the memory in DropShadow.image
     // and is responsible for releasing it when it's done with it
     DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
-                                   uint32_t len, int numGlyphs, uint32_t radius);
+            uint32_t len, int numGlyphs, uint32_t radius);
 
     GLuint getTexture() {
         checkInit();
@@ -154,7 +162,7 @@
         uint16_t mMaxWidth;
         uint32_t mCurrentRow;
         uint32_t mCurrentCol;
-        bool     mDirty;
+        bool mDirty;
 
         CacheTextureLine(uint16_t maxWidth, uint16_t maxHeight, uint32_t currentRow,
                 uint32_t currentCol):
@@ -237,9 +245,9 @@
 
     void computeGaussianWeights(float* weights, int32_t radius);
     void horizontalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
-                         int32_t width, int32_t height);
+            int32_t width, int32_t height);
     void verticalBlur(float* weights, int32_t radius, const uint8_t *source, uint8_t *dest,
-                         int32_t width, int32_t height);
+            int32_t width, int32_t height);
     void blurImage(uint8_t* image, int32_t width, int32_t height, int32_t radius);
 };
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 3c1fe2a..da5b9dd 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -41,6 +41,7 @@
 #define DEFAULT_PATH_CACHE_SIZE 6.0f
 #define DEFAULT_PATCH_CACHE_SIZE 100
 #define DEFAULT_GRADIENT_CACHE_SIZE 0.5f
+#define DEFAULT_DROP_SHADOW_CACHE_SIZE 1.0f
 
 #define REQUIRED_TEXTURE_UNITS_COUNT 3
 
@@ -107,7 +108,8 @@
         mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
         mGradientCache(MB(DEFAULT_GRADIENT_CACHE_SIZE)),
         mPathCache(MB(DEFAULT_PATH_CACHE_SIZE)),
-        mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
+        mPatchCache(DEFAULT_PATCH_CACHE_SIZE),
+        mDropShadowCache(MB(DEFAULT_DROP_SHADOW_CACHE_SIZE)) {
     LOGD("Create OpenGLRenderer");
 
     char property[PROPERTY_VALUE_MAX];
@@ -139,9 +141,18 @@
         LOGD("  Using default path cache size of %.2fMB", DEFAULT_PATH_CACHE_SIZE);
     }
 
+    if (property_get(PROPERTY_DROP_SHADOW_CACHE_SIZE, property, NULL) > 0) {
+        LOGD("  Setting drop shadow cache size to %sMB", property);
+        mDropShadowCache.setMaxSize(MB(atof(property)));
+    } else {
+        LOGD("  Using default drop shadow cache size of %.2fMB", DEFAULT_DROP_SHADOW_CACHE_SIZE);
+    }
+    mDropShadowCache.setFontRenderer(mFontRenderer);
+
     mCurrentProgram = NULL;
     mShader = NULL;
     mColorFilter = NULL;
+    mHasShadow = false;
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
 
@@ -163,6 +174,7 @@
     mPathCache.clear();
     mPatchCache.clear();
     mProgramCache.clear();
+    mDropShadowCache.clear();
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -550,6 +562,78 @@
     drawColorRect(left, top, right, bottom, color, mode);
 }
 
+void OpenGLRenderer::renderShadow(const ShadowTexture* texture, float x, float y,
+        SkXfermode::Mode mode) {
+    const float sx = x - texture->left + mShadowDx;
+    const float sy = y - texture->top + mShadowDy;
+
+    const GLfloat a = ((mShadowColor >> 24) & 0xFF) / 255.0f;
+    const GLfloat r = a * ((mShadowColor >> 16) & 0xFF) / 255.0f;
+    const GLfloat g = a * ((mShadowColor >>  8) & 0xFF) / 255.0f;
+    const GLfloat b = a * ((mShadowColor      ) & 0xFF) / 255.0f;
+
+    GLuint textureUnit = 0;
+    renderTextureAlpha8(texture, textureUnit, sx, sy, r, g, b, a, mode, false);
+}
+
+void OpenGLRenderer::renderTextureAlpha8(const Texture* texture, GLuint& textureUnit,
+        float x, float y, float r, float g, float b, float a, SkXfermode::Mode mode,
+        bool applyFilters) {
+     // Describe the required shaders
+     ProgramDescription description;
+     description.hasTexture = true;
+     description.hasAlpha8Texture = true;
+
+     if (applyFilters) {
+         if (mShader) {
+             mShader->describe(description, mExtensions);
+         }
+         if (mColorFilter) {
+             mColorFilter->describe(description, mExtensions);
+         }
+     }
+
+     // Build and use the appropriate shader
+     useProgram(mProgramCache.get(description));
+
+     // Setup the blending mode
+     chooseBlending(true, mode);
+     bindTexture(texture->id, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
+     glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
+
+     int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
+     glEnableVertexAttribArray(texCoordsSlot);
+
+     // Setup attributes
+     glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
+             gMeshStride, &mMeshVertices[0].position[0]);
+     glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
+             gMeshStride, &mMeshVertices[0].texture[0]);
+
+     // Setup uniforms
+     mModelView.loadTranslate(x, y, 0.0f);
+     mModelView.scale(texture->width, texture->height, 1.0f);
+     mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
+
+     glUniform4f(mCurrentProgram->color, r, g, b, a);
+
+     textureUnit++;
+     if (applyFilters) {
+         // Setup attributes and uniforms required by the shaders
+         if (mShader) {
+             mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
+         }
+         if (mColorFilter) {
+             mColorFilter->setupProgram(mCurrentProgram);
+         }
+     }
+
+     // Draw the mesh
+     glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
+
+     glDisableVertexAttribArray(texCoordsSlot);
+}
+
 #define kStdStrikeThru_Offset   (-6.0f / 21.0f)
 #define kStdUnderline_Offset    (1.0f / 9.0f)
 #define kStdUnderline_Thickness (1.0f / 18.0f)
@@ -578,6 +662,15 @@
     SkXfermode::Mode mode;
     getAlphaAndMode(paint, &alpha, &mode);
 
+    mFontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
+    if (mHasShadow) {
+        glActiveTexture(gTextureUnits[0]);
+        const ShadowTexture* shadow = mDropShadowCache.get(paint, text, bytesCount,
+                count, mShadowRadius);
+        const AutoTexture autoCleanup(shadow);
+        renderShadow(shadow, x, y, mode);
+    }
+
     uint32_t color = paint->getColor();
     const GLfloat a = alpha / 255.0f;
     const GLfloat r = a * ((color >> 16) & 0xFF) / 255.0f;
@@ -624,7 +717,6 @@
     }
 
     const Rect& clip = mSnapshot->getLocalClip();
-    mFontRenderer.setFont(paint, SkTypeface::UniqueID(paint->getTypeface()), paint->getTextSize());
     mFontRenderer.renderText(paint, &clip, text, 0, bytesCount, count, x, y);
 
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
@@ -693,55 +785,9 @@
     const GLfloat g = a * ((color >>  8) & 0xFF) / 255.0f;
     const GLfloat b = a * ((color      ) & 0xFF) / 255.0f;
 
-    // Describe the required shaders
-    ProgramDescription description;
-    description.hasTexture = true;
-    description.hasAlpha8Texture = true;
-    if (mShader) {
-        mShader->describe(description, mExtensions);
-    }
-    if (mColorFilter) {
-        mColorFilter->describe(description, mExtensions);
-    }
-
-    // Build and use the appropriate shader
-    useProgram(mProgramCache.get(description));
-
-    // Setup the blending mode
-    chooseBlending(true, mode);
-    bindTexture(texture->id, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
-    glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
-
-    int texCoordsSlot = mCurrentProgram->getAttrib("texCoords");
-    glEnableVertexAttribArray(texCoordsSlot);
-
-    // Setup attributes
-    glVertexAttribPointer(mCurrentProgram->position, 2, GL_FLOAT, GL_FALSE,
-            gMeshStride, &mMeshVertices[0].position[0]);
-    glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE,
-            gMeshStride, &mMeshVertices[0].texture[0]);
-
-    // Setup uniforms
-    mModelView.loadTranslate(texture->left - texture->offset,
-            texture->top - texture->offset, 0.0f);
-    mModelView.scale(texture->width, texture->height, 1.0f);
-    mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
-
-    glUniform4f(mCurrentProgram->color, r, g, b, a);
-
-    textureUnit++;
-    // Setup attributes and uniforms required by the shaders
-    if (mShader) {
-        mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
-    }
-    if (mColorFilter) {
-        mColorFilter->setupProgram(mCurrentProgram);
-    }
-
-    // Draw the mesh
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
-
-    glDisableVertexAttribArray(texCoordsSlot);
+    const float x = texture->left - texture->offset;
+    const float y = texture->top - texture->offset;
+    renderTextureAlpha8(texture, textureUnit, x, y, r, g, b, a, mode, true);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -772,6 +818,22 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Drop shadow
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetShadow() {
+    mHasShadow = false;
+}
+
+void OpenGLRenderer::setupShadow(float radius, float dx, float dy, int color) {
+    mHasShadow = true;
+    mShadowRadius = radius;
+    mShadowDx = dx;
+    mShadowDy = dy;
+    mShadowColor = color;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Drawing implementation
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 76783e9..948ff13 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -45,6 +45,7 @@
 #include "SkiaShader.h"
 #include "SkiaColorFilter.h"
 #include "PathCache.h"
+#include "TextDropShadowCache.h"
 
 namespace android {
 namespace uirenderer {
@@ -101,6 +102,9 @@
     void resetColorFilter();
     void setupColorFilter(SkiaColorFilter* filter);
 
+    void resetShadow();
+    void setupShadow(float radius, float dx, float dy, int color);
+
     void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
 
 private:
@@ -222,6 +226,34 @@
             GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0);
 
     /**
+     * Renders the specified shadow.
+     *
+     * @param texture The shadow texture
+     * @param x The x coordinate of the shadow
+     * @param y The y coordinate of the shadow
+     * @param mode The blending mode
+     */
+    void renderShadow(const ShadowTexture* texture, float x, float y, SkXfermode::Mode mode);
+
+    /**
+     * Renders the specified Alpha8 texture as a rectangle.
+     *
+     * @param texture The texture to render with
+     * @param textureUnit The texture unit to use, may be modified
+     * @param x The x coordinate of the rectangle to draw
+     * @param y The y coordinate of the rectangle to draw
+     * @param r The red component of the color
+     * @param g The green component of the color
+     * @param b The blue component of the color
+     * @param a The alpha component of the color
+     * @param mode The blending mode
+     * @param applyFilters Whether or not to take color filters and
+     *        shaders into account
+     */
+    void renderTextureAlpha8(const Texture* texture, GLuint& textureUnit, float x, float y,
+            float r, float g, float b, float a, SkXfermode::Mode mode, bool applyFilters);
+
+    /**
      * Resets the texture coordinates stored in mMeshVertices. Setting the values
      * back to default is achieved by calling:
      *
@@ -304,6 +336,13 @@
     // Font renderer
     FontRenderer mFontRenderer;
 
+    // Drop shadow
+    bool mHasShadow;
+    float mShadowRadius;
+    float mShadowDx;
+    float mShadowDy;
+    int mShadowColor;
+
     // Various caches
     TextureCache mTextureCache;
     LayerCache mLayerCache;
@@ -311,6 +350,7 @@
     ProgramCache mProgramCache;
     PathCache mPathCache;
     PatchCache mPatchCache;
+    TextDropShadowCache mDropShadowCache;
 }; // class OpenGLRenderer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 9a22dc0..10440ea 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -166,7 +166,7 @@
     glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
 
     texture->blend = true;
-    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, bitmap.rowBytesAsPixels(), texture->height, 0,
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
             GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.getPixels());
 
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 915b0be..7514b6f 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -27,6 +27,7 @@
 #define PROPERTY_LAYER_CACHE_SIZE "ro.hwui.layer_cache_size"
 #define PROPERTY_GRADIENT_CACHE_SIZE "ro.hwui.gradient_cache_size"
 #define PROPERTY_PATH_CACHE_SIZE "ro.hwui.path_cache_size"
+#define PROPERTY_DROP_SHADOW_CACHE_SIZE "ro.hwui.drop_shadow_cache_size"
 
 // These properties are defined in pixels
 #define PROPERTY_TEXT_CACHE_WIDTH "ro.hwui.text_cache_width"
diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp
new file mode 100644
index 0000000..aab5bd4
--- /dev/null
+++ b/libs/hwui/TextDropShadowCache.cpp
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include "TextDropShadowCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+TextDropShadowCache::TextDropShadowCache(uint32_t maxByteSize):
+        mCache(GenerationCache<ShadowText, ShadowTexture*>::kUnlimitedCapacity),
+        mSize(0), mMaxSize(maxByteSize) {
+    mCache.setOnEntryRemovedListener(this);
+}
+
+TextDropShadowCache::~TextDropShadowCache() {
+    mCache.clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Size management
+///////////////////////////////////////////////////////////////////////////////
+
+uint32_t TextDropShadowCache::getSize() {
+    return mSize;
+}
+
+uint32_t TextDropShadowCache::getMaxSize() {
+    return mMaxSize;
+}
+
+void TextDropShadowCache::setMaxSize(uint32_t maxSize) {
+    mMaxSize = maxSize;
+    while (mSize > mMaxSize) {
+        mCache.removeOldest();
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void TextDropShadowCache::operator()(ShadowText& text, ShadowTexture*& texture) {
+    const uint32_t size = texture->width * texture->height;
+    mSize -= size;
+
+    if (texture) {
+        glDeleteTextures(1, &texture->id);
+        delete texture;
+    }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void TextDropShadowCache::clear() {
+    mCache.clear();
+}
+
+ShadowTexture* TextDropShadowCache::get(SkPaint* paint, const char* text, uint32_t len,
+        int numGlyphs, uint32_t radius) {
+    ShadowText entry(paint, radius, len, text);
+    ShadowTexture* texture = mCache.get(entry);
+
+    if (!texture) {
+        FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(paint, text, 0,
+                len, numGlyphs, radius);
+
+        texture = new ShadowTexture;
+        texture->left = shadow.penX;
+        texture->top = shadow.penY;
+        texture->width = shadow.width;
+        texture->height = shadow.height;
+        texture->generation = 0;
+        texture->blend = true;
+
+        const uint32_t size = shadow.width * shadow.height;
+        // Don't even try to cache a bitmap that's bigger than the cache
+        if (size < mMaxSize) {
+            while (mSize + size > mMaxSize) {
+                mCache.removeOldest();
+            }
+        }
+
+        glGenTextures(1, &texture->id);
+
+        glBindTexture(GL_TEXTURE_2D, texture->id);
+        // Textures are Alpha8
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texture->width, texture->height, 0,
+                GL_ALPHA, GL_UNSIGNED_BYTE, shadow.image);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+        if (size < mMaxSize) {
+            mSize += size;
+            mCache.put(entry, texture);
+        } else {
+            texture->cleanup = true;
+        }
+
+        // Cleanup shadow
+        delete[] shadow.image;
+    }
+
+    return texture;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h
new file mode 100644
index 0000000..9c86187
--- /dev/null
+++ b/libs/hwui/TextDropShadowCache.h
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_UI_TEXT_DROP_SHADOW_CACHE_H
+#define ANDROID_UI_TEXT_DROP_SHADOW_CACHE_H
+
+#include <GLES2/gl2.h>
+
+#include <SkPaint.h>
+
+#include "GenerationCache.h"
+#include "FontRenderer.h"
+#include "Texture.h"
+
+namespace android {
+namespace uirenderer {
+
+struct ShadowText {
+    ShadowText() { }
+
+    ShadowText(SkPaint* paint, uint32_t radius, uint32_t len, const char* srcText):
+            paint(paint), radius(radius), len(len) {
+        text = new char[len];
+        memcpy(text, srcText, len);
+
+        hash = 0;
+        uint32_t multiplier = 1;
+        for (uint32_t i = 0; i < len; i++) {
+            hash += text[i] * multiplier;
+            uint32_t shifted = multiplier << 5;
+            multiplier = shifted - multiplier;
+        }
+    }
+
+    ShadowText(const ShadowText& shadow):
+            paint(shadow.paint), radius(shadow.radius), len(shadow.len), hash(shadow.hash) {
+        text = new char[len];
+        memcpy(text, shadow.text, shadow.len);
+    }
+
+    ~ShadowText() {
+        delete[] text;
+    }
+
+    SkPaint* paint;
+    uint32_t radius;
+    uint32_t len;
+    uint32_t hash;
+    char *text;
+
+    bool operator<(const ShadowText& rhs) const {
+        if (len < rhs.len) return true;
+        else if (len == rhs.len) {
+            if (radius < rhs.radius) return true;
+            else if (radius == rhs.radius) {
+                if (paint < rhs.paint) return true;
+                else if (paint == rhs.paint) {
+                    if (hash < rhs.hash) return true;
+                    if (hash == rhs.hash) {
+                        return strncmp(text, rhs.text, len) < 0;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+}; // struct ShadowText
+
+/**
+ * Alpha texture used to represent a shadow.
+ */
+struct ShadowTexture: public Texture {
+    ShadowTexture(): Texture() {
+    }
+
+    float left;
+    float top;
+}; // struct ShadowTexture
+
+class TextDropShadowCache: public OnEntryRemoved<ShadowText, ShadowTexture*> {
+public:
+    TextDropShadowCache(uint32_t maxByteSize);
+    ~TextDropShadowCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(ShadowText& text, ShadowTexture*& texture);
+
+    ShadowTexture* get(SkPaint* paint, const char* text, uint32_t len,
+            int numGlyphs, uint32_t radius);
+
+    /**
+     * Clears the cache. This causes all textures to be deleted.
+     */
+    void clear();
+
+    void setFontRenderer(FontRenderer& fontRenderer) {
+        mRenderer = &fontRenderer;
+    }
+
+    /**
+     * Sets the maximum size of the cache in bytes.
+     */
+    void setMaxSize(uint32_t maxSize);
+    /**
+     * Returns the maximum size of the cache in bytes.
+     */
+    uint32_t getMaxSize();
+    /**
+     * Returns the current size of the cache in bytes.
+     */
+    uint32_t getSize();
+
+private:
+    GenerationCache<ShadowText, ShadowTexture*> mCache;
+
+    uint32_t mSize;
+    uint32_t mMaxSize;
+    FontRenderer* mRenderer;
+}; // class TextDropShadowCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_TEXT_DROP_SHADOW_CACHE_H
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
index 59f665c..8af1b7b 100644
--- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/TextActivity.java
@@ -65,8 +65,16 @@
             canvas.drawText("Hello OpenGL renderer!", 100, 60, mMediumPaint);
             mMediumPaint.setTextAlign(Paint.Align.LEFT);
             canvas.drawText("Hello OpenGL renderer!", 100, 100, mMediumPaint);
+            mMediumPaint.setShadowLayer(2.5f, 0.0f, 0.0f, 0xff000000);
+            canvas.drawText("Hello OpenGL renderer!", 100, 150, mMediumPaint);
+            mMediumPaint.clearShadowLayer();
             canvas.drawText("Hello OpenGL renderer!", 100, 200, mLargePaint);
-            
+
+            mLargePaint.setShadowLayer(2.5f, 3.0f, 3.0f, 0xff000000);
+            canvas.drawText("Hello OpenGL renderer!", 100, 400, mLargePaint);
+            mLargePaint.setShadowLayer(3.0f, 3.0f, 3.0f, 0xff00ff00);
+            canvas.drawText("Hello OpenGL renderer!", 100, 500, mLargePaint);
+            mLargePaint.clearShadowLayer();
             
             canvas.drawText("Hello OpenGL renderer!", 500, 40, mStrikePaint);
             mStrikePaint.setStrikeThruText(true);