Draw n-patches using OpenGL.

Currently only tested with title bars and buttons.

Change-Id: I8263a9281898dc0e943b1b8412827fe55639b9d6
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 9146ba6..2d6263d 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -5,6 +5,7 @@
 	LayerCache.cpp \
 	Matrix.cpp \
 	OpenGLRenderer.cpp \
+	PatchCache.cpp \
 	Program.cpp \
 	TextureCache.cpp
 
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 586a05e..d4db782 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -17,6 +17,8 @@
 #ifndef ANDROID_UI_LAYER_H
 #define ANDROID_UI_LAYER_H
 
+#include <sys/types.h>
+
 #include <GLES2/gl2.h>
 
 #include <SkXfermode.h>
@@ -54,7 +56,7 @@
     bool operator==(const LayerSize& rhs) const {
         return width == rhs.width && height == rhs.height;
     }
-};
+}; // struct LayerSize
 
 /**
  * A layer has dimensions and is backed by an OpenGL texture.
diff --git a/libs/hwui/LayerCache.h b/libs/hwui/LayerCache.h
index adc6713..2580551 100644
--- a/libs/hwui/LayerCache.h
+++ b/libs/hwui/LayerCache.h
@@ -50,7 +50,7 @@
      * Used as a callback when an entry is removed from the cache.
      * Do not invoke directly.
      */
-    void operator()(LayerSize& bitmap, Layer*& texture);
+    void operator()(LayerSize& size, Layer*& layer);
 
     /**
      * Returns the layer of specified dimensions. If not suitable layer
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 027ed79..34da572 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -40,6 +40,7 @@
 
 #define DEFAULT_TEXTURE_CACHE_SIZE 20
 #define DEFAULT_LAYER_CACHE_SIZE 10
+#define DEFAULT_PATCH_CACHE_SIZE 100
 
 // Converts a number of mega-bytes into bytes
 #define MB(s) s * 1024 * 1024
@@ -95,7 +96,8 @@
 
 OpenGLRenderer::OpenGLRenderer():
         mTextureCache(MB(DEFAULT_TEXTURE_CACHE_SIZE)),
-        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)) {
+        mLayerCache(MB(DEFAULT_LAYER_CACHE_SIZE)),
+        mPatchCache(DEFAULT_PATCH_CACHE_SIZE) {
     LOGD("Create OpenGLRenderer");
 
     char property[PROPERTY_VALUE_MAX];
@@ -298,7 +300,6 @@
     GLuint previousFbo = snapshot->previous.get() ? snapshot->previous->fbo : 0;
     LayerSize size(right - left, bottom - top);
 
-    // TODO VERY IMPORTANT: Fix TextView to not call saveLayer() all the time
     Layer* layer = mLayerCache.get(size, previousFbo);
     if (!layer) {
         return false;
@@ -472,8 +473,110 @@
 
 void OpenGLRenderer::drawPatch(SkBitmap* bitmap, Res_png_9patch* patch,
         float left, float top, float right, float bottom, const SkPaint* paint) {
-    // TODO: Implement
-    LOGD("Draw 9patch, paddingLeft=%d", patch->paddingLeft);
+    const Texture* texture = mTextureCache.get(bitmap);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    const uint32_t width = patch->numXDivs;
+    const uint32_t height = patch->numYDivs;
+
+    Patch* mesh = mPatchCache.get(patch);
+
+    const uint32_t xStretchCount = (width + 1) >> 1;
+    const uint32_t yStretchCount = (height + 1) >> 1;
+
+    const int32_t* xDivs = &patch->xDivs[0];
+    const int32_t* yDivs = &patch->yDivs[0];
+
+    float xStretch = 0;
+    float yStretch = 0;
+    float xStretchTex = 0;
+    float yStretchTex = 0;
+
+    const float meshWidth = right - left;
+
+    const float bitmapWidth = float(bitmap->width());
+    const float bitmapHeight = float(bitmap->height());
+
+    if (xStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < width; i += 2) {
+            stretchSize += xDivs[i] - xDivs[i - 1];
+        }
+        xStretchTex = (stretchSize / bitmapWidth) / xStretchCount;
+        const float fixed = bitmapWidth - stretchSize;
+        xStretch = (right - left - fixed) / xStretchCount;
+    }
+
+    if (yStretchCount > 0) {
+        uint32_t stretchSize = 0;
+        for (uint32_t i = 1; i < height; i += 2) {
+            stretchSize += yDivs[i] - yDivs[i - 1];
+        }
+        yStretchTex = (stretchSize / bitmapHeight) / yStretchCount;
+        const float fixed = bitmapHeight - stretchSize;
+        yStretch = (bottom - top - fixed) / yStretchCount;
+    }
+
+    float vy = 0.0f;
+    float ty = 0.0f;
+    TextureVertex* vertex = mesh->vertices;
+
+    generateVertices(vertex, 0.0f, 0.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+    vertex += width + 2;
+
+    for (uint32_t y = 0; y < height; y++) {
+        if (y & 1) {
+            vy += yStretch;
+            ty += yStretchTex;
+        } else {
+            const float step = float(yDivs[y]);
+            vy += step;
+            ty += step / bitmapHeight;
+        }
+        generateVertices(vertex, vy, ty, xDivs, width, xStretch, xStretchTex,
+                meshWidth, bitmapWidth);
+        vertex += width + 2;
+    }
+
+    generateVertices(vertex, bottom - top, 1.0f, xDivs, width, xStretch, xStretchTex,
+            meshWidth, bitmapWidth);
+
+    // Specify right and bottom as +1.0f from left/top to prevent scaling since the
+    // patch mesh already defines the final size
+    drawTextureMesh(left, top, left + 1.0f, top + 1.0f, texture->id, alpha, mode, texture->blend,
+            true, &mesh->vertices[0].position[0], &mesh->vertices[0].texture[0], mesh->indices,
+            mesh->indicesCount);
+}
+
+void OpenGLRenderer::generateVertices(TextureVertex* vertex, float y, float v,
+        const int32_t xDivs[], uint32_t xCount, float xStretch, float xStretchTex,
+        float width, float widthTex) {
+    float vx = 0.0f;
+    float tx = 0.0f;
+
+    TextureVertex::set(vertex, vx, y, tx, v);
+    vertex++;
+
+    for (uint32_t x = 0; x < xCount; x++) {
+        if (x & 1) {
+            vx += xStretch;
+            tx += xStretchTex;
+        } else {
+            const float step = float(xDivs[x]);
+            vx += step;
+            tx += step / widthTex;
+        }
+
+        TextureVertex::set(vertex, vx, y, tx, v);
+        vertex++;
+    }
+
+    TextureVertex::set(vertex, width, y, 1.0f, v);
+    vertex++;
 }
 
 void OpenGLRenderer::drawColor(int color, SkXfermode::Mode mode) {
@@ -538,6 +641,13 @@
 
 void OpenGLRenderer::drawTextureRect(float left, float top, float right, float bottom,
         GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied) {
+    drawTextureMesh(left, top, right, bottom, texture, alpha, mode, blend, isPremultiplied,
+            &mDrawTextureVertices[0].position[0], &mDrawTextureVertices[0].texture[0], NULL);
+}
+
+void OpenGLRenderer::drawTextureMesh(float left, float top, float right, float bottom,
+        GLuint texture, float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied,
+        GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
 
@@ -560,20 +670,21 @@
     glActiveTexture(GL_TEXTURE0);
     glUniform1i(mDrawTextureShader->sampler, 0);
 
-    const GLvoid* p = &mDrawTextureVertices[0].position[0];
-    const GLvoid* t = &mDrawTextureVertices[0].texture[0];
-
     glEnableVertexAttribArray(mDrawTextureShader->position);
     glVertexAttribPointer(mDrawTextureShader->position, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, p);
+            gDrawTextureVertexStride, vertices);
 
     glEnableVertexAttribArray(mDrawTextureShader->texCoords);
     glVertexAttribPointer(mDrawTextureShader->texCoords, 2, GL_FLOAT, GL_FALSE,
-            gDrawTextureVertexStride, t);
+            gDrawTextureVertexStride, texCoords);
 
     glVertexAttrib4f(mDrawTextureShader->color, 1.0f, 1.0f, 1.0f, alpha);
 
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+    if (!indices) {
+        glDrawArrays(GL_TRIANGLE_STRIP, 0, gDrawTextureVertexCount);
+    } else {
+        glDrawElements(GL_TRIANGLES, elementsCount, GL_UNSIGNED_SHORT, indices);
+    }
 
     glDisableVertexAttribArray(mDrawTextureShader->position);
     glDisableVertexAttribArray(mDrawTextureShader->texCoords);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 9da4b8e..b46e9d3 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -32,10 +32,10 @@
 #include "Program.h"
 #include "Rect.h"
 #include "Snapshot.h"
-#include "Texture.h"
-#include "Layer.h"
 #include "TextureCache.h"
 #include "LayerCache.h"
+#include "PatchCache.h"
+#include "Vertex.h"
 
 namespace android {
 namespace uirenderer {
@@ -45,22 +45,6 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
- * Simple structure to describe a vertex with a position.
- * This is used to draw filled rectangles without a texture.
- */
-struct SimpleVertex {
-    float position[2];
-}; // struct SimpleVertex
-
-/**
- * Simple structure to describe a vertex with a position and a texture.
- */
-struct TextureVertex {
-    float position[2];
-    float texture[2];
-}; // struct TextureVertex
-
-/**
  * Structure mapping Skia xfermodes to OpenGL blending factors.
  */
 struct Blender {
@@ -198,6 +182,13 @@
             float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied = false);
 
     /**
+     * TODO: documentation
+     */
+    void drawTextureMesh(float left, float top, float right, float bottom, GLuint texture,
+            float alpha, SkXfermode::Mode mode, bool blend, bool isPremultiplied,
+            GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount = 0);
+
+    /**
      * Resets the texture coordinates stored in mDrawTextureVertices. Setting the values
      * back to default is achieved by calling:
      *
@@ -220,6 +211,12 @@
      */
     inline void getAlphaAndMode(const SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
 
+    /**
+     * TODO: documentation
+     */
+    inline void generateVertices(TextureVertex* vertex, float y, float v, const int32_t xDivs[],
+            uint32_t xCount, float xStretch, float xStretchTex, float width, float widthTex);
+
     // Dimensions of the drawing surface
     int mWidth, mHeight;
 
@@ -243,9 +240,9 @@
     // Used to draw textured quads
     TextureVertex mDrawTextureVertices[4];
 
-    // Used to cache all drawBitmap textures
     TextureCache mTextureCache;
     LayerCache mLayerCache;
+    PatchCache mPatchCache;
 }; // class OpenGLRenderer
 
 }; // namespace uirenderer
diff --git a/libs/hwui/Patch.h b/libs/hwui/Patch.h
new file mode 100644
index 0000000..c6af18a
--- /dev/null
+++ b/libs/hwui/Patch.h
@@ -0,0 +1,129 @@
+/*
+ * 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_PATCH_H
+#define ANDROID_UI_PATCH_H
+
+#include <sys/types.h>
+
+#include <cstring>
+
+#include "Vertex.h"
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Description of a patch.
+ */
+struct PatchDescription {
+    PatchDescription(): xCount(0), yCount(0) { }
+    PatchDescription(const uint32_t xCount, const uint32_t yCount):
+            xCount(xCount), yCount(yCount) { }
+    PatchDescription(const PatchDescription& description):
+            xCount(description.xCount), yCount(description.yCount) { }
+
+    uint32_t xCount;
+    uint32_t yCount;
+
+    bool operator<(const PatchDescription& rhs) const {
+        if (xCount == rhs.xCount) {
+            return yCount < rhs.yCount;
+        }
+        return xCount < rhs.xCount;
+    }
+
+    bool operator==(const PatchDescription& rhs) const {
+        return xCount == rhs.xCount && yCount == rhs.yCount;
+    }
+}; // struct PatchDescription
+
+/**
+ * An OpenGL patch. This contains an array of vertices and an array of
+ * indices to render the vertices.
+ */
+struct Patch {
+    Patch(const uint32_t xCount, const uint32_t yCount): xCount(xCount + 2), yCount(yCount + 2) {
+        verticesCount = (xCount + 2) * (yCount + 2);
+        vertices = new TextureVertex[verticesCount];
+
+        // 2 triangles per patch, 3 vertices per triangle
+        indicesCount = (xCount + 1) * (yCount + 1) * 2 * 3;
+        indices = new uint16_t[indicesCount];
+
+        const uint32_t xNum = xCount + 1;
+        const uint32_t yNum = yCount + 1;
+
+        uint16_t* startIndices = indices;
+        uint32_t n = 0;
+        for (uint32_t y = 0; y < yNum; y++) {
+            for (uint32_t x = 0; x < xNum; x++) {
+                *startIndices++ = n;
+                *startIndices++ = n + 1;
+                *startIndices++ = n + xNum + 2;
+
+                *startIndices++ = n;
+                *startIndices++ = n + xNum + 2;
+                *startIndices++ = n + xNum + 1;
+
+                n += 1;
+            }
+            n += 1;
+        }
+    }
+
+    ~Patch() {
+        delete indices;
+        delete vertices;
+    }
+
+    void dump() {
+        LOGD("Vertices [");
+        for (uint32_t y = 0; y < yCount; y++) {
+            char buffer[512];
+            buffer[0] = '\0';
+            uint32_t offset = 0;
+            for (uint32_t x = 0; x < xCount; x++) {
+                TextureVertex* v = &vertices[y * xCount + x];
+                offset += sprintf(&buffer[offset], " (%.4f,%.4f)-(%.4f,%.4f)",
+                        v->position[0], v->position[1], v->texture[0], v->texture[1]);
+            }
+            LOGD("  [%s ]", buffer);
+        }
+        LOGD("]\nIndices [ ");
+        char buffer[4096];
+        buffer[0] = '\0';
+        uint32_t offset = 0;
+        for (uint32_t i = 0; i < indicesCount; i++) {
+            offset += sprintf(&buffer[offset], "%d ", indices[i]);
+        }
+        LOGD("  %s\n]", buffer);
+    }
+
+    uint32_t xCount;
+    uint32_t yCount;
+
+    uint16_t* indices;
+    uint32_t indicesCount;
+
+    TextureVertex* vertices;
+    uint32_t verticesCount;
+}; // struct Patch
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATCH_H
diff --git a/libs/hwui/PatchCache.cpp b/libs/hwui/PatchCache.cpp
new file mode 100644
index 0000000..694e3fd
--- /dev/null
+++ b/libs/hwui/PatchCache.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <utils/Log.h>
+#include <utils/ResourceTypes.h>
+
+#include "PatchCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Constructors/destructor
+///////////////////////////////////////////////////////////////////////////////
+
+PatchCache::PatchCache(uint32_t maxEntries): mCache(maxEntries) {
+}
+
+PatchCache::~PatchCache() {
+    clear();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Callbacks
+///////////////////////////////////////////////////////////////////////////////
+
+void PatchCache::operator()(PatchDescription& description, Patch*& mesh) {
+    if (mesh) delete mesh;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Caching
+///////////////////////////////////////////////////////////////////////////////
+
+void PatchCache::clear() {
+    mCache.setOnEntryRemovedListener(this);
+    mCache.clear();
+    mCache.setOnEntryRemovedListener(NULL);
+}
+
+Patch* PatchCache::get(const Res_png_9patch* patch) {
+    const uint32_t width = patch->numXDivs;
+    const uint32_t height = patch->numYDivs;
+    const PatchDescription description(width, height);
+
+    Patch* mesh = mCache.get(description);
+    if (!mesh) {
+        PATCH_LOGD("Creating new patch mesh, w=%d h=%d", width, height);
+        mesh = new Patch(width, height);
+        mCache.put(description, mesh);
+    }
+
+    return mesh;
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/PatchCache.h b/libs/hwui/PatchCache.h
new file mode 100644
index 0000000..de95087
--- /dev/null
+++ b/libs/hwui/PatchCache.h
@@ -0,0 +1,65 @@
+/*
+ * 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_PATCH_CACHE_H
+#define ANDROID_UI_PATCH_CACHE_H
+
+#include "Patch.h"
+#include "GenerationCache.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Defines
+///////////////////////////////////////////////////////////////////////////////
+
+// Debug
+#define DEBUG_PATCHES 0
+
+// Debug
+#if DEBUG_PATCHES
+    #define PATCH_LOGD(...) LOGD(__VA_ARGS__)
+#else
+    #define PATCH_LOGD(...)
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// Cache
+///////////////////////////////////////////////////////////////////////////////
+
+class PatchCache: public OnEntryRemoved<PatchDescription, Patch*> {
+public:
+    PatchCache(uint32_t maxCapacity);
+    ~PatchCache();
+
+    /**
+     * Used as a callback when an entry is removed from the cache.
+     * Do not invoke directly.
+     */
+    void operator()(PatchDescription& description, Patch*& mesh);
+
+    Patch* get(const Res_png_9patch* patch);
+    void clear();
+
+private:
+    GenerationCache<PatchDescription, Patch*> mCache;
+}; // class PatchCache
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_PATCH_CACHE_H
diff --git a/libs/hwui/Vertex.h b/libs/hwui/Vertex.h
new file mode 100644
index 0000000..208d2a8
--- /dev/null
+++ b/libs/hwui/Vertex.h
@@ -0,0 +1,49 @@
+/*
+ * 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_VERTEX_H
+#define ANDROID_UI_VERTEX_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * Simple structure to describe a vertex with a position.
+ * This is used to draw filled rectangles without a texture.
+ */
+struct SimpleVertex {
+    float position[2];
+}; // struct SimpleVertex
+
+/**
+ * Simple structure to describe a vertex with a position and a texture.
+ */
+struct TextureVertex {
+    float position[2];
+    float texture[2];
+
+    static inline void set(TextureVertex* vertex, float x, float y, float u, float v) {
+        vertex[0].position[0] = x;
+        vertex[0].position[1] = y;
+        vertex[0].texture[0] = u;
+        vertex[0].texture[1] = v;
+    }
+}; // struct TextureVertex
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_VERTEX_H
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 79eed4e..e09e70f 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -69,6 +69,15 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        
+        <activity
+                android:name="NinePatchesActivity"
+                android:label="_9patch">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
                 
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java
new file mode 100644
index 0000000..dc8fbcc
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/NinePatchesActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.google.android.test.hwui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class NinePatchesActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        FrameLayout layout = new FrameLayout(this);
+        Button b = new Button(this);
+        b.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
+                FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
+        b.setText("9 patches");
+        layout.addView(b);
+        
+        setContentView(layout);
+    }
+}