Refcount 9-patches and properly handle GC events

This change adds refcounting of Res_png_9patch instances, the native
data structure used to represent 9-patches. The Dalvik NinePatch class
now holds a native pointer instead of a Dalvik byte[]. This pointer
is used whenever we need to draw the 9-patch (software or hardware.)

Since we are now tracking garbage collection of NinePatch objects
libhwui's PatchCache must keep a list of free blocks in the VBO
used to store the meshes.

This change also removes unnecessary instances tracking from
GLES20DisplayList. Bitmaps and 9-patches are refcounted at the
native level and do not need to be tracked by the Dalvik layer.

Change-Id: Ib8682d573a538aaf1945f8ec5a9bd5da5d16f74b
diff --git a/api/current.txt b/api/current.txt
index facaabe..ee4ef67 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -9155,8 +9155,11 @@
     method public void draw(android.graphics.Canvas, android.graphics.RectF);
     method public void draw(android.graphics.Canvas, android.graphics.Rect);
     method public void draw(android.graphics.Canvas, android.graphics.Rect, android.graphics.Paint);
+    method public android.graphics.Bitmap getBitmap();
     method public int getDensity();
     method public int getHeight();
+    method public java.lang.String getName();
+    method public android.graphics.Paint getPaint();
     method public final android.graphics.Region getTransparentRegion(android.graphics.Rect);
     method public int getWidth();
     method public final boolean hasAlpha();
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 1f35c1d..a441c39 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -781,7 +781,7 @@
         int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mChunk,
+            nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
                     dst.left, dst.top, dst.right, dst.bottom, nativePaint);
         } finally {
             if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
@@ -796,14 +796,14 @@
         int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
         try {
             final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mChunk,
+            nDrawPatch(mRenderer, bitmap.mNativeBitmap, patch.mNativeChunk,
                     dst.left, dst.top, dst.right, dst.bottom, nativePaint);
         } finally {
             if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
         }
     }
 
-    private static native void nDrawPatch(int renderer, int bitmap, byte[] chunks,
+    private static native void nDrawPatch(int renderer, int bitmap, int chunk,
             float left, float top, float right, float bottom, int paint);
 
     @Override
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 9c5b33d..8b2a2ef 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -16,9 +16,7 @@
 
 package android.view;
 
-import android.graphics.Bitmap;
 import android.graphics.Matrix;
-import android.graphics.NinePatch;
 
 import java.util.ArrayList;
 
@@ -26,12 +24,6 @@
  * An implementation of display list for OpenGL ES 2.0.
  */
 class GLES20DisplayList extends DisplayList {
-    // These lists ensure that any Bitmaps and DisplayLists recorded by a DisplayList are kept
-    // alive as long as the DisplayList is alive.  The Bitmap and DisplayList lists
-    // are populated by the GLES20RecordingCanvas during appropriate drawing calls and are
-    // cleared at the start of a new drawing frame or when the view is detached from the window.
-    private ArrayList<Bitmap> mBitmaps;
-    private ArrayList<NinePatch> mNinePatches;
     private ArrayList<DisplayList> mChildDisplayLists;
 
     private GLES20RecordingCanvas mCanvas;
@@ -89,21 +81,9 @@
     }
 
     void clearReferences() {
-        if (mBitmaps != null) mBitmaps.clear();
-        if (mNinePatches != null) mNinePatches.clear();
         if (mChildDisplayLists != null) mChildDisplayLists.clear();
     }
 
-    ArrayList<Bitmap> getBitmaps() {
-        if (mBitmaps == null) mBitmaps = new ArrayList<Bitmap>(5);
-        return mBitmaps;
-    }
-
-    ArrayList<NinePatch> getNinePatches() {
-        if (mNinePatches == null) mNinePatches = new ArrayList<NinePatch>(5);
-        return mNinePatches;
-    }
-
     ArrayList<DisplayList> getChildDisplayLists() {
         if (mChildDisplayLists == null) mChildDisplayLists = new ArrayList<DisplayList>();
         return mChildDisplayLists;
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 273947f..b6fc38d 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -16,15 +16,7 @@
 
 package android.view;
 
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
-import android.graphics.Matrix;
-import android.graphics.NinePatch;
-import android.graphics.Paint;
-import android.graphics.Path;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.Shader;
 import android.util.Pools.SynchronizedPool;
 
 /**
@@ -70,222 +62,10 @@
         return getDisplayList(nativeDisplayList);
     }
 
-    private void recordShaderBitmap(Paint paint) {
-        if (paint != null) {
-            final Shader shader = paint.getShader();
-            if (shader instanceof BitmapShader) {
-                mDisplayList.getBitmaps().add(((BitmapShader) shader).mBitmap);
-            }
-        }
-    }
-
-    @Override
-    public void drawPatch(NinePatch patch, RectF dst, Paint paint) {
-        super.drawPatch(patch, dst, paint);
-        mDisplayList.getBitmaps().add(patch.getBitmap());
-        mDisplayList.getNinePatches().add(patch);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
-        super.drawBitmap(bitmap, left, top, paint);
-        mDisplayList.getBitmaps().add(bitmap);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
-        super.drawBitmap(bitmap, matrix, paint);
-        mDisplayList.getBitmaps().add(bitmap);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
-        super.drawBitmap(bitmap, src, dst, paint);
-        mDisplayList.getBitmaps().add(bitmap);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
-        super.drawBitmap(bitmap, src, dst, paint);
-        mDisplayList.getBitmaps().add(bitmap);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(int[] colors, int offset, int stride, float x, float y, int width,
-            int height, boolean hasAlpha, Paint paint) {
-        super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmap(int[] colors, int offset, int stride, int x, int y, int width,
-            int height, boolean hasAlpha, Paint paint) {
-        super.drawBitmap(colors, offset, stride, x, y, width, height, hasAlpha, paint);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts,
-            int vertOffset, int[] colors, int colorOffset, Paint paint) {
-        super.drawBitmapMesh(bitmap, meshWidth, meshHeight, verts, vertOffset,
-                colors, colorOffset, paint);
-        mDisplayList.getBitmaps().add(bitmap);
-        // Shaders in the Paint are ignored when drawing a Bitmap
-    }
-
-    @Override
-    public void drawCircle(float cx, float cy, float radius, Paint paint) {
-        super.drawCircle(cx, cy, radius, paint);
-        recordShaderBitmap(paint);
-    }
-
     @Override
     public int drawDisplayList(DisplayList displayList, Rect dirty, int flags) {
         int status = super.drawDisplayList(displayList, dirty, flags);
         mDisplayList.getChildDisplayLists().add(displayList);
         return status;
     }
-
-    @Override
-    public void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) {
-        super.drawLine(startX, startY, stopX, stopY, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawLines(float[] pts, int offset, int count, Paint paint) {
-        super.drawLines(pts, offset, count, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawLines(float[] pts, Paint paint) {
-        super.drawLines(pts, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawOval(RectF oval, Paint paint) {
-        super.drawOval(oval, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPaint(Paint paint) {
-        super.drawPaint(paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPath(Path path, Paint paint) {
-        super.drawPath(path, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPoint(float x, float y, Paint paint) {
-        super.drawPoint(x, y, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPoints(float[] pts, int offset, int count, Paint paint) {
-        super.drawPoints(pts, offset, count, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPoints(float[] pts, Paint paint) {
-        super.drawPoints(pts, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPosText(char[] text, int index, int count, float[] pos, Paint paint) {
-        super.drawPosText(text, index, count, pos, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawPosText(String text, float[] pos, Paint paint) {
-        super.drawPosText(text, pos, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawRect(float left, float top, float right, float bottom, Paint paint) {
-        super.drawRect(left, top, right, bottom, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawRoundRect(RectF rect, float rx, float ry, Paint paint) {
-        super.drawRoundRect(rect, rx, ry, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawText(char[] text, int index, int count, float x, float y, Paint paint) {
-        super.drawText(text, index, count, x, y, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
-        super.drawText(text, start, end, x, y, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawText(String text, int start, int end, float x, float y, Paint paint) {
-        super.drawText(text, start, end, x, y, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawText(String text, float x, float y, Paint paint) {
-        super.drawText(text, x, y, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawTextOnPath(char[] text, int index, int count, Path path, float hOffset,
-            float vOffset, Paint paint) {
-        super.drawTextOnPath(text, index, count, path, hOffset, vOffset, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint) {
-        super.drawTextOnPath(text, path, hOffset, vOffset, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawTextRun(char[] text, int index, int count, int contextIndex, int contextCount,
-            float x, float y, int dir, Paint paint) {
-        super.drawTextRun(text, index, count, contextIndex, contextCount, x, y, dir, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawTextRun(CharSequence text, int start, int end, int contextStart,
-            int contextEnd, float x, float y, int dir, Paint paint) {
-        super.drawTextRun(text, start, end, contextStart, contextEnd, x, y, dir, paint);
-        recordShaderBitmap(paint);
-    }
-
-    @Override
-    public void drawVertices(VertexMode mode, int vertexCount, float[] verts, int vertOffset,
-            float[] texs, int texOffset, int[] colors, int colorOffset, short[] indices,
-            int indexOffset, int indexCount, Paint paint) {
-        super.drawVertices(mode, vertexCount, verts, vertOffset, texs, texOffset, colors,
-                colorOffset, indices, indexOffset, indexCount, paint);
-        recordShaderBitmap(paint);
-    }
 }
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 684b1c1..7e6aeae 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -21,22 +21,30 @@
 #include <androidfw/ResourceTypes.h>
 #include <utils/Log.h>
 
+#include <Caches.h>
+
 #include "SkCanvas.h"
 #include "SkRegion.h"
 #include "GraphicsJNI.h"
 
 #include "JNIHelp.h"
 
-extern void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds,
-                const SkBitmap& bitmap, const android::Res_png_9patch& chunk,
-                           const SkPaint* paint, SkRegion** outRegion);
+extern void NinePatch_Draw(SkCanvas* canvas, const SkRect& bounds, const SkBitmap& bitmap,
+        const android::Res_png_9patch& chunk, const SkPaint* paint, SkRegion** outRegion);
 
 using namespace android;
 
+/**
+ * IMPORTANT NOTE: 9patch chunks can be manipuated either as an array of bytes
+ * or as a Res_png_9patch instance. It is important to note that the size of the
+ * array required to hold a 9patch chunk is greater than sizeof(Res_png_9patch).
+ * The code below manipulates chunks as Res_png_9patch* types to draw and as
+ * int8_t* to allocate and free the backing storage.
+ */
+
 class SkNinePatchGlue {
 public:
-    static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj)
-    {
+    static jboolean isNinePatchChunk(JNIEnv* env, jobject, jbyteArray obj) {
         if (NULL == obj) {
             return false;
         }
@@ -45,126 +53,110 @@
         }
         const jbyte* array = env->GetByteArrayElements(obj, 0);
         if (array != NULL) {
-            const Res_png_9patch* chunk =
-                                reinterpret_cast<const Res_png_9patch*>(array);
+            const Res_png_9patch* chunk = reinterpret_cast<const Res_png_9patch*>(array);
             int8_t wasDeserialized = chunk->wasDeserialized;
-            env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array),
-                                          JNI_ABORT);
+            env->ReleaseByteArrayElements(obj, const_cast<jbyte*>(array), JNI_ABORT);
             return wasDeserialized != -1;
         }
         return false;
     }
 
-    static void validateNinePatchChunk(JNIEnv* env, jobject, jint, jbyteArray obj)
-    {
-        if (env->GetArrayLength(obj) < (int) (sizeof(Res_png_9patch))) {
+    static int8_t* validateNinePatchChunk(JNIEnv* env, jobject, jint, jbyteArray obj) {
+        size_t chunkSize = env->GetArrayLength(obj);
+        if (chunkSize < (int) (sizeof(Res_png_9patch))) {
             jniThrowRuntimeException(env, "Array too small for chunk.");
-            return;
+            return NULL;
         }
 
-        // XXX Also check that dimensions are correct.
+        int8_t* storage = new int8_t[chunkSize];
+        // This call copies the content of the jbyteArray
+        env->GetByteArrayRegion(obj, 0, chunkSize, reinterpret_cast<jbyte*>(storage));
+        // Deserialize in place, return the array we just allocated
+        return (int8_t*) Res_png_9patch::deserialize(storage);
     }
 
-    static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds,
-                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint,
-                      jint destDensity, jint srcDensity)
-    {
-        size_t chunkSize = env->GetArrayLength(chunkObj);
-        void* storage = alloca(chunkSize);
-        env->GetByteArrayRegion(chunkObj, 0, chunkSize,
-                                reinterpret_cast<jbyte*>(storage));
-        if (!env->ExceptionCheck()) {
-            // need to deserialize the chunk
-            Res_png_9patch* chunk = static_cast<Res_png_9patch*>(storage);
-            assert(chunkSize == chunk->serializedSize());
-            // this relies on deserialization being done in place
-            Res_png_9patch::deserialize(chunk);
+    static void finalize(JNIEnv* env, jobject, int8_t* patch) {
+#ifdef USE_OPENGL_RENDERER
+        if (android::uirenderer::Caches::hasInstance()) {
+            Res_png_9patch* p = (Res_png_9patch*) patch;
+            android::uirenderer::Caches::getInstance().resourceCache.destructor(p);
+            return;
+        }
+#endif // USE_OPENGL_RENDERER
+        delete[] patch;
+    }
 
-            if (destDensity == srcDensity || destDensity == 0
-                    || srcDensity == 0) {
-                ALOGV("Drawing unscaled 9-patch: (%g,%g)-(%g,%g)",
-                        SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
-                        SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom));
-                NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
-            } else {
-                canvas->save();
+    static void draw(JNIEnv* env, SkCanvas* canvas, SkRect& bounds, const SkBitmap* bitmap,
+            Res_png_9patch* chunk, const SkPaint* paint, jint destDensity, jint srcDensity) {
+        if (destDensity == srcDensity || destDensity == 0 || srcDensity == 0) {
+            ALOGV("Drawing unscaled 9-patch: (%g,%g)-(%g,%g)",
+                    SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+                    SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom));
+            NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+        } else {
+            canvas->save();
 
-                SkScalar scale = SkFloatToScalar(destDensity / (float)srcDensity);
-                canvas->translate(bounds.fLeft, bounds.fTop);
-                canvas->scale(scale, scale);
+            SkScalar scale = SkFloatToScalar(destDensity / (float)srcDensity);
+            canvas->translate(bounds.fLeft, bounds.fTop);
+            canvas->scale(scale, scale);
 
-                bounds.fRight = SkScalarDiv(bounds.fRight-bounds.fLeft, scale);
-                bounds.fBottom = SkScalarDiv(bounds.fBottom-bounds.fTop, scale);
-                bounds.fLeft = bounds.fTop = 0;
+            bounds.fRight = SkScalarDiv(bounds.fRight-bounds.fLeft, scale);
+            bounds.fBottom = SkScalarDiv(bounds.fBottom-bounds.fTop, scale);
+            bounds.fLeft = bounds.fTop = 0;
 
-                ALOGV("Drawing scaled 9-patch: (%g,%g)-(%g,%g) srcDensity=%d destDensity=%d",
-                        SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
-                        SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom),
-                        srcDensity, destDensity);
+            ALOGV("Drawing scaled 9-patch: (%g,%g)-(%g,%g) srcDensity=%d destDensity=%d",
+                    SkScalarToFloat(bounds.fLeft), SkScalarToFloat(bounds.fTop),
+                    SkScalarToFloat(bounds.fRight), SkScalarToFloat(bounds.fBottom),
+                    srcDensity, destDensity);
 
-                NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
+            NinePatch_Draw(canvas, bounds, *bitmap, *chunk, paint, NULL);
 
-                canvas->restore();
-            }
+            canvas->restore();
         }
     }
 
     static void drawF(JNIEnv* env, jobject, SkCanvas* canvas, jobject boundsRectF,
-                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint,
-                      jint destDensity, jint srcDensity)
-    {
+            const SkBitmap* bitmap, Res_png_9patch* chunk, const SkPaint* paint,
+            jint destDensity, jint srcDensity) {
         SkASSERT(canvas);
         SkASSERT(boundsRectF);
         SkASSERT(bitmap);
-        SkASSERT(chunkObj);
+        SkASSERT(chunk);
         // paint is optional
 
-        SkRect      bounds;
+        SkRect bounds;
         GraphicsJNI::jrectf_to_rect(env, boundsRectF, &bounds);
 
-        draw(env, canvas, bounds, bitmap, chunkObj, paint, destDensity, srcDensity);
+        draw(env, canvas, bounds, bitmap, chunk, paint, destDensity, srcDensity);
     }
 
     static void drawI(JNIEnv* env, jobject, SkCanvas* canvas, jobject boundsRect,
-                      const SkBitmap* bitmap, jbyteArray chunkObj, const SkPaint* paint,
-                      jint destDensity, jint srcDensity)
-    {
+            const SkBitmap* bitmap, Res_png_9patch* chunk, const SkPaint* paint,
+            jint destDensity, jint srcDensity) {
         SkASSERT(canvas);
         SkASSERT(boundsRect);
         SkASSERT(bitmap);
-        SkASSERT(chunkObj);
+        SkASSERT(chunk);
         // paint is optional
 
-        SkRect      bounds;
+        SkRect bounds;
         GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
-        draw(env, canvas, bounds, bitmap, chunkObj, paint, destDensity, srcDensity);
+        draw(env, canvas, bounds, bitmap, chunk, paint, destDensity, srcDensity);
     }
 
-    static jint getTransparentRegion(JNIEnv* env, jobject,
-                    const SkBitmap* bitmap, jbyteArray chunkObj,
-                    jobject boundsRect)
-    {
+    static jint getTransparentRegion(JNIEnv* env, jobject, const SkBitmap* bitmap,
+            Res_png_9patch* chunk, jobject boundsRect) {
         SkASSERT(bitmap);
-        SkASSERT(chunkObj);
+        SkASSERT(chunk);
         SkASSERT(boundsRect);
 
-        SkRect      bounds;
+        SkRect bounds;
         GraphicsJNI::jrect_to_rect(env, boundsRect, &bounds);
-        size_t chunkSize = env->GetArrayLength(chunkObj);
-        void* storage = alloca(chunkSize);
-        env->GetByteArrayRegion(chunkObj, 0, chunkSize,
-                                reinterpret_cast<jbyte*>(storage));
-        if (!env->ExceptionCheck()) {
-            // need to deserialize the chunk
-            Res_png_9patch* chunk = static_cast<Res_png_9patch*>(storage);
-            assert(chunkSize == chunk->serializedSize());
-            // this relies on deserialization being done in place
-            Res_png_9patch::deserialize(chunk);
-            SkRegion* region = NULL;
-            NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, &region);
-            return (jint)region;
-        }
-        return 0;
+
+        SkRegion* region = NULL;
+        NinePatch_Draw(NULL, bounds, *bitmap, *chunk, NULL, &region);
+
+        return (jint) region;
     }
 
 };
@@ -174,18 +166,16 @@
 #include <android_runtime/AndroidRuntime.h>
 
 static JNINativeMethod gNinePatchMethods[] = {
-    { "isNinePatchChunk", "([B)Z",                      (void*)SkNinePatchGlue::isNinePatchChunk   },
-    { "validateNinePatchChunk", "(I[B)V",               (void*)SkNinePatchGlue::validateNinePatchChunk   },
-    { "nativeDraw", "(ILandroid/graphics/RectF;I[BIII)V", (void*)SkNinePatchGlue::drawF   },
-    { "nativeDraw", "(ILandroid/graphics/Rect;I[BIII)V",  (void*)SkNinePatchGlue::drawI   },
-    { "nativeGetTransparentRegion", "(I[BLandroid/graphics/Rect;)I",
-                                                        (void*)SkNinePatchGlue::getTransparentRegion   }
+    { "isNinePatchChunk", "([B)Z",                        (void*) SkNinePatchGlue::isNinePatchChunk },
+    { "validateNinePatchChunk", "(I[B)I",                 (void*) SkNinePatchGlue::validateNinePatchChunk },
+    { "nativeFinalize", "(I)V",                           (void*) SkNinePatchGlue::finalize },
+    { "nativeDraw", "(ILandroid/graphics/RectF;IIIII)V",  (void*) SkNinePatchGlue::drawF },
+    { "nativeDraw", "(ILandroid/graphics/Rect;IIIII)V",   (void*) SkNinePatchGlue::drawI },
+    { "nativeGetTransparentRegion", "(IILandroid/graphics/Rect;)I",
+                                                          (void*) SkNinePatchGlue::getTransparentRegion }
 };
 
-int register_android_graphics_NinePatch(JNIEnv* env)
-{
+int register_android_graphics_NinePatch(JNIEnv* env) {
     return android::AndroidRuntime::registerNativeMethods(env,
-                                                       "android/graphics/NinePatch",
-                                                       gNinePatchMethods,
-                                                       SK_ARRAY_COUNT(gNinePatchMethods));
+            "android/graphics/NinePatch", gNinePatchMethods, SK_ARRAY_COUNT(gNinePatchMethods));
 }
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 5b6cff0..87ebbd2 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -432,15 +432,9 @@
 }
 
 static void android_view_GLES20Canvas_drawPatch(JNIEnv* env, jobject clazz,
-        OpenGLRenderer* renderer, SkBitmap* bitmap, jbyteArray chunks,
+        OpenGLRenderer* renderer, SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, SkPaint* paint) {
-    jbyte* storage = env->GetByteArrayElements(chunks, NULL);
-    Res_png_9patch* patch = reinterpret_cast<Res_png_9patch*>(storage);
-    Res_png_9patch::deserialize(patch);
-
     renderer->drawPatch(bitmap, patch, left, top, right, bottom, paint);
-
-    env->ReleaseByteArrayElements(chunks, storage, 0);
 }
 
 static void android_view_GLES20Canvas_drawColor(JNIEnv* env, jobject clazz,
@@ -1031,7 +1025,7 @@
 
     { "nDrawBitmapMesh",    "(IIII[FI[III)V",  (void*) android_view_GLES20Canvas_drawBitmapMesh },
 
-    { "nDrawPatch",         "(II[BFFFFI)V",    (void*) android_view_GLES20Canvas_drawPatch },
+    { "nDrawPatch",         "(IIIFFFFI)V",     (void*) android_view_GLES20Canvas_drawPatch },
 
     { "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
     { "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index ab1c328..528d9de 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -18,11 +18,8 @@
 
 
 /**
- * The NinePatch class permits drawing a bitmap in nine sections.
- * The four corners are unscaled; the four edges are scaled in one axis,
- * and the middle is scaled in both axes. Normally, the middle is 
- * transparent so that the patch can provide a selection about a rectangle.
- * Essentially, it allows the creation of custom graphics that will scale the 
+ * The NinePatch class permits drawing a bitmap in nine or more sections.
+ * Essentially, it allows the creation of custom graphics that will scale the
  * way that you define, when content added within the image exceeds the normal
  * bounds of the graphic. For a thorough explanation of a NinePatch image, 
  * read the discussion in the 
@@ -36,19 +33,23 @@
  */
 public class NinePatch {
     private final Bitmap mBitmap;
+
     /**
+     * Used by native code. This pointer is an instance of Res_png_9patch*.
+     *
      * @hide
      */
-    public final byte[] mChunk;
+    public final int mNativeChunk;
+
     private Paint mPaint;
-    private String mSrcName;  // Useful for debugging
+    private String mSrcName;
 
     /**
      * Create a drawable projection from a bitmap to nine patches.
      *
-     * @param bitmap    The bitmap describing the patches.
-     * @param chunk     The 9-patch data chunk describing how the underlying
-     *                  bitmap is split apart and drawn.
+     * @param bitmap The bitmap describing the patches.
+     * @param chunk The 9-patch data chunk describing how the underlying bitmap
+     *              is split apart and drawn.
      */
     public NinePatch(Bitmap bitmap, byte[] chunk) {
         this(bitmap, chunk, null);
@@ -57,16 +58,15 @@
     /** 
      * Create a drawable projection from a bitmap to nine patches.
      *
-     * @param bitmap    The bitmap describing the patches.
-     * @param chunk     The 9-patch data chunk describing how the underlying
-     *                  bitmap is split apart and drawn.
-     * @param srcName   The name of the source for the bitmap. Might be null.
+     * @param bitmap The bitmap describing the patches.
+     * @param chunk The 9-patch data chunk describing how the underlying
+     *              bitmap is split apart and drawn.
+     * @param srcName The name of the source for the bitmap. Might be null.
      */
     public NinePatch(Bitmap bitmap, byte[] chunk, String srcName) {
         mBitmap = bitmap;
-        mChunk = chunk;
         mSrcName = srcName;
-        validateNinePatchChunk(mBitmap.ni(), chunk);
+        mNativeChunk = validateNinePatchChunk(mBitmap.ni(), chunk);
     }
 
     /**
@@ -74,69 +74,102 @@
      */
     public NinePatch(NinePatch patch) {
         mBitmap = patch.mBitmap;
-        mChunk = patch.mChunk;
         mSrcName = patch.mSrcName;
         if (patch.mPaint != null) {
             mPaint = new Paint(patch.mPaint);
         }
-        validateNinePatchChunk(mBitmap.ni(), mChunk);
+        // No need to validate the 9patch chunk again, it was done by
+        // the instance we're copying from
+        mNativeChunk = patch.mNativeChunk;
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nativeFinalize(mNativeChunk);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Returns the name of this NinePatch object if one was specified
+     * when calling the constructor.
+     */
+    public String getName() {
+        return mSrcName;
+    }
+
+    /**
+     * Returns the paint used to draw this NinePatch. The paint can be null.
+     *
+     * @see #setPaint(Paint)
+     * @see #draw(Canvas, Rect)
+     * @see #draw(Canvas, RectF)
+     */
+    public Paint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Sets the paint to use when drawing the NinePatch.
+     *
+     * @param p The paint that will be used to draw this NinePatch.
+     *
+     * @see #getPaint()
+     * @see #draw(Canvas, Rect)
+     * @see #draw(Canvas, RectF)
+     */
     public void setPaint(Paint p) {
         mPaint = p;
     }
 
     /**
-     * @hide
+     * Returns the bitmap used to draw this NinePatch.
      */
     public Bitmap getBitmap() {
         return mBitmap;
     }
     
     /** 
-     * Draw a bitmap of nine patches.
+     * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
      *
-     * @param canvas    A container for the current matrix and clip used to draw the bitmap.
-     * @param location  Where to draw the bitmap.
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
      */
     public void draw(Canvas canvas, RectF location) {
         canvas.drawPatch(this, location, mPaint);
     }
 
     /** 
-     * Draw a bitmap of nine patches.
+     * Draws the NinePatch. This method will use the paint returned by {@link #getPaint()}.
      *
-     * @param canvas    A container for the current matrix and clip used to draw the bitmap.
-     * @param location  Where to draw the bitmap.
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
      */
     public void draw(Canvas canvas, Rect location) {
         canvas.drawPatch(this, location, mPaint);
     }
 
     /** 
-     * Draw a bitmap of nine patches.
+     * Draws the NinePatch. This method will ignore the paint returned
+     * by {@link #getPaint()} and use the specified paint instead.
      *
-     * @param canvas    A container for the current matrix and clip used to draw the bitmap.
-     * @param location  Where to draw the bitmap.
-     * @param paint     The Paint to draw through.
+     * @param canvas A container for the current matrix and clip used to draw the NinePatch.
+     * @param location Where to draw the NinePatch.
+     * @param paint The Paint to draw through.
      */
     public void draw(Canvas canvas, Rect location, Paint paint) {
         canvas.drawPatch(this, location, paint);
     }
 
-    /**
-     * @hide
-     */
     void drawSoftware(Canvas canvas, RectF location, Paint paint) {
-        nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mChunk,
+        nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk,
                 paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity);
     }
 
-    /**
-     * @hide
-     */
     void drawSoftware(Canvas canvas, Rect location, Paint paint) {
-        nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mChunk,
+        nativeDraw(canvas.mNativeCanvas, location, mBitmap.ni(), mNativeChunk,
                 paint != null ? paint.mNativePaint : 0, canvas.mDensity, mBitmap.mDensity);
     }
 
@@ -147,32 +180,67 @@
     public int getDensity() {
         return mBitmap.mDensity;
     }
-    
+
+    /**
+     * Returns the intrinsic width, in pixels, of this NinePatch. This is equivalent
+     * to querying the width of the underlying bitmap returned by {@link #getBitmap()}.
+     */
     public int getWidth() {
         return mBitmap.getWidth();
     }
 
+    /**
+     * Returns the intrinsic height, in pixels, of this NinePatch. This is equivalent
+     * to querying the height of the underlying bitmap returned by {@link #getBitmap()}.
+     */
     public int getHeight() {
         return mBitmap.getHeight();
     }
 
+    /**
+     * Indicates whether this NinePatch contains transparent or translucent pixels.
+     * This is equivalent to calling <code>getBitmap().hasAlpha()</code> on this
+     * NinePatch.
+     */
     public final boolean hasAlpha() {
         return mBitmap.hasAlpha();
     }
 
-    public final Region getTransparentRegion(Rect location) {
-        int r = nativeGetTransparentRegion(mBitmap.ni(), mChunk, location);
+    /**
+     * Returns a {@link Region} representing the parts of the NinePatch that are
+     * completely transparent.
+     *
+     * @param bounds The location and size of the NinePatch.
+     *
+     * @return null if the NinePatch has no transparent region to
+     * report, else a {@link Region} holding the parts of the specified bounds
+     * that are transparent.
+     */
+    public final Region getTransparentRegion(Rect bounds) {
+        int r = nativeGetTransparentRegion(mBitmap.ni(), mNativeChunk, bounds);
         return r != 0 ? new Region(r) : null;
     }
-    
+
+    /**
+     * Verifies that the specified byte array is a valid 9-patch data chunk.
+     *
+     * @param chunk A byte array representing a 9-patch data chunk.
+     *
+     * @return True if the specified byte array represents a 9-patch data chunk,
+     *         false otherwise.
+     */
     public native static boolean isNinePatchChunk(byte[] chunk);
 
-    private static native void validateNinePatchChunk(int bitmap, byte[] chunk);
+    /**
+     * Validates the 9-patch chunk and throws an exception if the chunk is invalid.
+     * If validation is successful, this method returns a native Res_png_9patch*
+     * object used by the renderers.
+     */
+    private static native int validateNinePatchChunk(int bitmap, byte[] chunk);
+    private static native void nativeFinalize(int chunk);
     private static native void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
-                                          byte[] c, int paint_instance_or_null,
-                                          int destDensity, int srcDensity);
+            int c, int paint_instance_or_null, int destDensity, int srcDensity);
     private static native void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
-                                          byte[] c, int paint_instance_or_null,
-                                          int destDensity, int srcDensity);
-    private static native int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location);
+            int c, int paint_instance_or_null, int destDensity, int srcDensity);
+    private static native int nativeGetTransparentRegion(int bitmap, int chunk, Rect location);
 }
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 1089b7c..6de8c8c 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -286,6 +286,7 @@
 void Caches::clearGarbage() {
     textureCache.clearGarbage();
     pathCache.clearGarbage();
+    patchCache.clearGarbage();
 
     Vector<DisplayList*> displayLists;
     Vector<Layer*> layers;
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 0d4dd1a..cebfd26 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -100,6 +100,10 @@
         caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
     }
 
+    for (size_t i = 0; i < mPatchResources.size(); i++) {
+        caches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i));
+    }
+
     for (size_t i = 0; i < mShaders.size(); i++) {
         caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
         caches.resourceCache.destructorLocked(mShaders.itemAt(i));
@@ -134,6 +138,7 @@
     mBitmapResources.clear();
     mOwnedBitmapResources.clear();
     mFilterResources.clear();
+    mPatchResources.clear();
     mShaders.clear();
     mSourcePaths.clear();
     mPaints.clear();
@@ -201,6 +206,13 @@
         caches.resourceCache.incrementRefcountLocked(resource);
     }
 
+    const Vector<Res_png_9patch*>& patchResources = recorder.getPatchResources();
+    for (size_t i = 0; i < patchResources.size(); i++) {
+        Res_png_9patch* resource = patchResources.itemAt(i);
+        mPatchResources.add(resource);
+        caches.resourceCache.incrementRefcountLocked(resource);
+    }
+
     const Vector<SkiaShader*>& shaders = recorder.getShaders();
     for (size_t i = 0; i < shaders.size(); i++) {
         SkiaShader* resource = shaders.itemAt(i);
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index 1417df7..194be9e 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -30,8 +30,11 @@
 #include <utils/SortedVector.h>
 #include <utils/String8.h>
 #include <utils/Vector.h>
+
 #include <cutils/compiler.h>
 
+#include <androidfw/ResourceTypes.h>
+
 #include "utils/LinearAllocator.h"
 
 #include "Debug.h"
@@ -484,6 +487,7 @@
     Vector<SkBitmap*> mBitmapResources;
     Vector<SkBitmap*> mOwnedBitmapResources;
     Vector<SkiaColorFilter*> mFilterResources;
+    Vector<Res_png_9patch*> mPatchResources;
 
     Vector<SkPaint*> mPaints;
     Vector<SkPath*> mPaths;
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 5d23e1d..9113092 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -57,6 +57,10 @@
         mCaches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
     }
 
+    for (size_t i = 0; i < mPatchResources.size(); i++) {
+        mCaches.resourceCache.decrementRefcountLocked(mPatchResources.itemAt(i));
+    }
+
     for (size_t i = 0; i < mShaders.size(); i++) {
         mCaches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
     }
@@ -74,6 +78,7 @@
     mBitmapResources.clear();
     mOwnedBitmapResources.clear();
     mFilterResources.clear();
+    mPatchResources.clear();
     mSourcePaths.clear();
 
     mShaders.clear();
@@ -318,6 +323,7 @@
 status_t DisplayListRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, SkPaint* paint) {
     bitmap = refBitmap(bitmap);
+    patch = refPatch(patch);
     paint = refPaint(paint);
 
     addDrawOp(new (alloc()) DrawPatchOp(bitmap, patch, left, top, right, bottom, paint));
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index 85d6107..03f50c8 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -156,6 +156,10 @@
         return mFilterResources;
     }
 
+    const Vector<Res_png_9patch*>& getPatchResources() const {
+        return mPatchResources;
+    }
+
     const Vector<SkiaShader*>& getShaders() const {
         return mShaders;
     }
@@ -315,9 +319,16 @@
         return colorFilter;
     }
 
+    inline Res_png_9patch* refPatch(Res_png_9patch* patch) {
+        mPatchResources.add(patch);
+        mCaches.resourceCache.incrementRefcount(patch);
+        return patch;
+    }
+
     Vector<SkBitmap*> mBitmapResources;
     Vector<SkBitmap*> mOwnedBitmapResources;
     Vector<SkiaColorFilter*> mFilterResources;
+    Vector<Res_png_9patch*> mPatchResources;
 
     Vector<SkPaint*> mPaints;
     DefaultKeyedVector<SkPaint*, SkPaint*> mPaintMap;
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
index dc69d7f..dc0d98c 100644
--- a/libs/hwui/PatchCache.cpp
+++ b/libs/hwui/PatchCache.cpp
@@ -32,7 +32,7 @@
 
 PatchCache::PatchCache():
         mSize(0), mCache(LruCache<PatchDescription, Patch*>::kUnlimitedCapacity),
-        mMeshBuffer(0), mGenerationId(0) {
+        mMeshBuffer(0), mFreeBlocks(NULL), mGenerationId(0) {
     char property[PROPERTY_VALUE_MAX];
     if (property_get(PROPERTY_PATCH_CACHE_SIZE, property, NULL) > 0) {
         INIT_LOGD("  Setting patch cache size to %skB", property);
@@ -97,14 +97,130 @@
         delete i.value();
     }
     mCache.clear();
+
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        BufferBlock* next = block->next;
+        delete block;
+        block = next;
+    }
+    mFreeBlocks = NULL;
+}
+
+void PatchCache::remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch) {
+    LruCache<PatchDescription, Patch*>::Iterator i(mCache);
+    while (i.next()) {
+        const PatchDescription& key = i.key();
+        if (key.getPatch() == patch) {
+            patchesToRemove.push(patch_pair_t(&key, i.value()));
+        }
+    }
+}
+
+void PatchCache::removeDeferred(Res_png_9patch* patch) {
+    Mutex::Autolock _l(mLock);
+    mGarbage.push(patch);
+}
+
+void PatchCache::clearGarbage() {
+    Vector<patch_pair_t> patchesToRemove;
+
+    { // scope for the mutex
+        Mutex::Autolock _l(mLock);
+        size_t count = mGarbage.size();
+        for (size_t i = 0; i < count; i++) {
+            remove(patchesToRemove, mGarbage[i]);
+        }
+        mGarbage.clear();
+    }
+
+    // TODO: We could sort patchesToRemove by offset to merge
+    // adjacent free blocks
+    for (size_t i = 0; i < patchesToRemove.size(); i++) {
+        const patch_pair_t& pair = patchesToRemove[i];
+
+        // Add a new free block to the list
+        const Patch* patch = pair.getSecond();
+        BufferBlock* block = new BufferBlock(patch->offset, patch->getSize());
+        block->next = mFreeBlocks;
+        mFreeBlocks = block;
+
+        mSize -= patch->getSize();
+
+        mCache.remove(*pair.getFirst());
+    }
+
+#if DEBUG_PATCHES
+    if (patchesToRemove.size() > 0) {
+        dumpFreeBlocks("Removed garbage");
+    }
+#endif
 }
 
 void PatchCache::createVertexBuffer() {
     glBufferData(GL_ARRAY_BUFFER, mMaxSize, NULL, GL_DYNAMIC_DRAW);
     mSize = 0;
+    mFreeBlocks = new BufferBlock(0, mMaxSize);
     mGenerationId++;
 }
 
+/**
+ * Sets the mesh's offsets and copies its associated vertices into
+ * the mesh buffer (VBO).
+ */
+void PatchCache::setupMesh(Patch* newMesh, TextureVertex* vertices) {
+    // This call ensures the VBO exists and that it is bound
+    init(Caches::getInstance());
+
+    // If we're running out of space, let's clear the entire cache
+    uint32_t size = newMesh->getSize();
+    if (mSize + size > mMaxSize) {
+        clearCache();
+        createVertexBuffer();
+    }
+
+    // Find a block where we can fit the mesh
+    BufferBlock* previous = NULL;
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        // The mesh fits
+        if (block->size >= size) {
+            break;
+        }
+        previous = block;
+        block = block->next;
+    }
+
+    // We have enough space left in the buffer, but it's
+    // too fragmented, let's clear the cache
+    if (!block) {
+        clearCache();
+        createVertexBuffer();
+        previous = NULL;
+        block = mFreeBlocks;
+    }
+
+    // Copy the 9patch mesh in the VBO
+    newMesh->offset = (GLintptr) (block->offset);
+    newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
+    glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+
+    // Remove the block since we've used it entirely
+    if (block->size == size) {
+        if (previous) {
+            previous->next = block->next;
+        } else {
+            mFreeBlocks = block->next;
+        }
+    } else {
+        // Resize the block now that it's occupied
+        block->offset += size;
+        block->size -= size;
+    }
+
+    mSize += size;
+}
+
 const Patch* PatchCache::get(const AssetAtlas::Entry* entry,
         const uint32_t bitmapWidth, const uint32_t bitmapHeight,
         const float pixelWidth, const float pixelHeight, const Res_png_9patch* patch) {
@@ -117,6 +233,7 @@
         TextureVertex* vertices;
 
         if (entry) {
+            // An atlas entry has a UV mapper
             vertices = newMesh->createMesh(bitmapWidth, bitmapHeight,
                     pixelWidth, pixelHeight, entry->uvMapper, patch);
         } else {
@@ -125,24 +242,13 @@
         }
 
         if (vertices) {
-            // This call ensures the VBO exists and that it is bound
-            init(Caches::getInstance());
-
-            // TODO: Simply remove the oldest items until we have enough room
-            // This will require to keep a list of free blocks in the VBO
-            uint32_t size = newMesh->getSize();
-            if (mSize + size > mMaxSize) {
-                clearCache();
-                createVertexBuffer();
-            }
-
-            newMesh->offset = (GLintptr) mSize;
-            newMesh->textureOffset = newMesh->offset + gMeshTextureOffset;
-            mSize += size;
-
-            glBufferSubData(GL_ARRAY_BUFFER, newMesh->offset, size, vertices);
+            setupMesh(newMesh, vertices);
         }
 
+#if DEBUG_PATCHES
+        dumpFreeBlocks("Adding patch");
+#endif
+
         mCache.put(description, newMesh);
         return newMesh;
     }
@@ -150,5 +256,17 @@
     return mesh;
 }
 
+#if DEBUG_PATCHES
+void PatchCache::dumpFreeBlocks(const char* prefix) {
+    String8 dump;
+    BufferBlock* block = mFreeBlocks;
+    while (block) {
+        dump.appendFormat("->(%d, %d)", block->offset, block->size);
+        block = block->next;
+    }
+    ALOGD("%s: Free blocks%s", prefix, dump.string());
+}
+#endif
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
index 1829b89..9f2c9a5 100644
--- a/libs/hwui/PatchCache.h
+++ b/libs/hwui/PatchCache.h
@@ -26,6 +26,7 @@
 #include "AssetAtlas.h"
 #include "Debug.h"
 #include "Patch.h"
+#include "utils/Pair.h"
 
 namespace android {
 namespace uirenderer {
@@ -74,10 +75,20 @@
         return mGenerationId;
     }
 
-private:
-    void clearCache();
-    void createVertexBuffer();
+    /**
+     * Removes the entries associated with the specified 9-patch. This is meant
+     * to be called from threads that are not the EGL context thread (GC thread
+     * on the VM side for instance.)
+     */
+    void removeDeferred(Res_png_9patch* patch);
 
+    /**
+     * Process deferred removals.
+     */
+    void clearGarbage();
+
+
+private:
     struct PatchDescription {
         PatchDescription(): mPatch(NULL), mBitmapWidth(0), mBitmapHeight(0),
                 mPixelWidth(0), mPixelHeight(0) {
@@ -91,6 +102,8 @@
 
         hash_t hash() const;
 
+        const Res_png_9patch* getPatch() const { return mPatch; }
+
         static int compare(const PatchDescription& lhs, const PatchDescription& rhs);
 
         bool operator==(const PatchDescription& other) const {
@@ -124,14 +137,50 @@
 
     }; // struct PatchDescription
 
+    /**
+     * A buffer block represents an empty range in the mesh buffer
+     * that can be used to store vertices.
+     *
+     * The patch cache maintains a linked-list of buffer blocks
+     * to track available regions of memory in the VBO.
+     */
+    struct BufferBlock {
+        BufferBlock(uint32_t offset, uint32_t size): offset(offset), size(size), next(NULL) {
+        }
+
+        uint32_t offset;
+        uint32_t size;
+
+        BufferBlock* next;
+    }; // struct BufferBlock
+
+    typedef Pair<const PatchDescription*, Patch*> patch_pair_t;
+
+    void clearCache();
+    void createVertexBuffer();
+
+    void setupMesh(Patch* newMesh, TextureVertex* vertices);
+
+    void remove(Vector<patch_pair_t>& patchesToRemove, Res_png_9patch* patch);
+
+#if DEBUG_PATCHES
+    void dumpFreeBlocks(const char* prefix);
+#endif
+
     uint32_t mMaxSize;
     uint32_t mSize;
 
     LruCache<PatchDescription, Patch*> mCache;
 
     GLuint mMeshBuffer;
+    // First available free block inside the mesh buffer
+    BufferBlock* mFreeBlocks;
 
     uint32_t mGenerationId;
+
+    // Garbage tracking, required to handle GC events on the VM side
+    Vector<Res_png_9patch*> mGarbage;
+    mutable Mutex mLock;
 }; // class PatchCache
 
 }; // namespace uirenderer
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 3ab40da..70ab6e7 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -350,8 +350,7 @@
 // Paths
 ///////////////////////////////////////////////////////////////////////////////
 
-void PathCache::remove(const path_pair_t& pair) {
-    Vector<PathDescription> pathsToRemove;
+void PathCache::remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair) {
     LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
 
     while (i.next()) {
@@ -362,10 +361,6 @@
             pathsToRemove.push(key);
         }
     }
-
-    for (size_t i = 0; i < pathsToRemove.size(); i++) {
-        mCache.remove(pathsToRemove.itemAt(i));
-    }
 }
 
 void PathCache::removeDeferred(SkPath* path) {
@@ -374,12 +369,20 @@
 }
 
 void PathCache::clearGarbage() {
-    Mutex::Autolock l(mLock);
-    size_t count = mGarbage.size();
-    for (size_t i = 0; i < count; i++) {
-        remove(mGarbage.itemAt(i));
+    Vector<PathDescription> pathsToRemove;
+
+    { // scope for the mutex
+        Mutex::Autolock l(mLock);
+        size_t count = mGarbage.size();
+        for (size_t i = 0; i < count; i++) {
+            remove(pathsToRemove, mGarbage.itemAt(i));
+        }
+        mGarbage.clear();
     }
-    mGarbage.clear();
+
+    for (size_t i = 0; i < pathsToRemove.size(); i++) {
+        mCache.remove(pathsToRemove.itemAt(i));
+    }
 }
 
 /**
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index a191f0e..16d20a8 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -269,7 +269,7 @@
      * Removes an entry.
      * The pair must define first=path, second=sourcePath
      */
-    void remove(const path_pair_t& pair);
+    void remove(Vector<PathDescription>& pathsToRemove, const path_pair_t& pair);
 
     /**
      * Ensures there is enough space in the cache for a texture of the specified
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 347bd78..58fa21c 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#define LOG_TAG "OpenGLRenderer"
+
 #include <SkPixelRef.h>
 #include "ResourceCache.h"
 #include "Caches.h"
@@ -79,6 +81,10 @@
     incrementRefcount((void*) filterResource, kColorFilter);
 }
 
+void ResourceCache::incrementRefcount(Res_png_9patch* patchResource) {
+    incrementRefcount((void*) patchResource, kNinePatch);
+}
+
 void ResourceCache::incrementRefcount(Layer* layerResource) {
     incrementRefcount((void*) layerResource, kLayer);
 }
@@ -113,6 +119,10 @@
     incrementRefcountLocked((void*) filterResource, kColorFilter);
 }
 
+void ResourceCache::incrementRefcountLocked(Res_png_9patch* patchResource) {
+    incrementRefcountLocked((void*) patchResource, kNinePatch);
+}
+
 void ResourceCache::incrementRefcountLocked(Layer* layerResource) {
     incrementRefcountLocked((void*) layerResource, kLayer);
 }
@@ -142,6 +152,10 @@
     decrementRefcount((void*) filterResource);
 }
 
+void ResourceCache::decrementRefcount(Res_png_9patch* patchResource) {
+    decrementRefcount((void*) patchResource);
+}
+
 void ResourceCache::decrementRefcount(Layer* layerResource) {
     decrementRefcount((void*) layerResource);
 }
@@ -179,6 +193,10 @@
     decrementRefcountLocked((void*) filterResource);
 }
 
+void ResourceCache::decrementRefcountLocked(Res_png_9patch* patchResource) {
+    decrementRefcountLocked((void*) patchResource);
+}
+
 void ResourceCache::decrementRefcountLocked(Layer* layerResource) {
     decrementRefcountLocked((void*) layerResource);
 }
@@ -265,6 +283,30 @@
     }
 }
 
+void ResourceCache::destructor(Res_png_9patch* resource) {
+    Mutex::Autolock _l(mLock);
+    destructorLocked(resource);
+}
+
+void ResourceCache::destructorLocked(Res_png_9patch* resource) {
+    ssize_t index = mCache->indexOfKey(resource);
+    ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
+    if (ref == NULL) {
+        if (Caches::hasInstance()) {
+            Caches::getInstance().patchCache.removeDeferred(resource);
+        }
+        // If we're not tracking this resource, just delete it
+        // A Res_png_9patch is actually an array of byte that's larger
+        // than sizeof(Res_png_9patch). It must be freed as an array.
+        delete[] (int8_t*) resource;
+        return;
+    }
+    ref->destroyed = true;
+    if (ref->refCount == 0) {
+        deleteResourceReferenceLocked(resource, ref);
+    }
+}
+
 /**
  * Return value indicates whether resource was actually recycled, which happens when RefCnt
  * reaches 0.
@@ -335,6 +377,16 @@
                 delete filter;
             }
             break;
+            case kNinePatch: {
+                if (Caches::hasInstance()) {
+                    Caches::getInstance().patchCache.removeDeferred((Res_png_9patch*) resource);
+                }
+                // A Res_png_9patch is actually an array of byte that's larger
+                // than sizeof(Res_png_9patch). It must be freed as an array.
+                int8_t* patch = (int8_t*) resource;
+                delete[] patch;
+            }
+            break;
             case kLayer: {
                 Layer* layer = (Layer*) resource;
                 Caches::getInstance().deleteLayerDeferred(layer);
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index ab493e5..ea0c1b5 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -22,7 +22,11 @@
 #include <SkBitmap.h>
 #include <SkiaColorFilter.h>
 #include <SkiaShader.h>
+
 #include <utils/KeyedVector.h>
+
+#include <androidfw/ResourceTypes.h>
+
 #include "Layer.h"
 
 namespace android {
@@ -35,6 +39,7 @@
     kBitmap,
     kShader,
     kColorFilter,
+    kNinePatch,
     kPath,
     kLayer
 };
@@ -69,35 +74,41 @@
     void incrementRefcount(SkBitmap* resource);
     void incrementRefcount(SkiaShader* resource);
     void incrementRefcount(SkiaColorFilter* resource);
+    void incrementRefcount(Res_png_9patch* resource);
     void incrementRefcount(Layer* resource);
 
     void incrementRefcountLocked(SkPath* resource);
     void incrementRefcountLocked(SkBitmap* resource);
     void incrementRefcountLocked(SkiaShader* resource);
     void incrementRefcountLocked(SkiaColorFilter* resource);
+    void incrementRefcountLocked(Res_png_9patch* resource);
     void incrementRefcountLocked(Layer* resource);
 
     void decrementRefcount(SkBitmap* resource);
     void decrementRefcount(SkPath* resource);
     void decrementRefcount(SkiaShader* resource);
     void decrementRefcount(SkiaColorFilter* resource);
+    void decrementRefcount(Res_png_9patch* resource);
     void decrementRefcount(Layer* resource);
 
     void decrementRefcountLocked(SkBitmap* resource);
     void decrementRefcountLocked(SkPath* resource);
     void decrementRefcountLocked(SkiaShader* resource);
     void decrementRefcountLocked(SkiaColorFilter* resource);
+    void decrementRefcountLocked(Res_png_9patch* resource);
     void decrementRefcountLocked(Layer* resource);
 
     void destructor(SkPath* resource);
     void destructor(SkBitmap* resource);
     void destructor(SkiaShader* resource);
     void destructor(SkiaColorFilter* resource);
+    void destructor(Res_png_9patch* resource);
 
     void destructorLocked(SkPath* resource);
     void destructorLocked(SkBitmap* resource);
     void destructorLocked(SkiaShader* resource);
     void destructorLocked(SkiaColorFilter* resource);
+    void destructorLocked(Res_png_9patch* resource);
 
     bool recycle(SkBitmap* resource);
     bool recycleLocked(SkBitmap* resource);