Add support for SweepGradient in the GL renderer.

Change-Id: I7aa397ed4e34655ead9ba1f5b4ce087665e0f022
diff --git a/core/jni/android/graphics/Shader.cpp b/core/jni/android/graphics/Shader.cpp
index 9202429..344669c 100644
--- a/core/jni/android/graphics/Shader.cpp
+++ b/core/jni/android/graphics/Shader.cpp
@@ -318,6 +318,59 @@
     return s;
 }
 
+static SkiaShader* SweepGradient_postCreate1(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, jintArray colorArray, jfloatArray posArray) {
+#ifdef USE_OPENGL_RENDERER
+    size_t count = env->GetArrayLength(colorArray);
+    const jint* colorValues = env->GetIntArrayElements(colorArray, NULL);
+
+    jfloat* storedPositions = new jfloat[count];
+    uint32_t* storedColors = new uint32_t[count];
+    for (size_t i = 0; i < count; i++) {
+        storedColors[i] = static_cast<uint32_t>(colorValues[i]);
+    }
+
+    if (posArray) {
+        AutoJavaFloatArray autoPos(env, posArray, count);
+        const float* posValues = autoPos.ptr();
+        for (size_t i = 0; i < count; i++) {
+            storedPositions[i] = posValues[i];
+        }
+    } else {
+        storedPositions[0] = 0.0f;
+        storedPositions[1] = 1.0f;
+    }
+
+    SkiaShader* skiaShader = new SkiaSweepGradientShader(x, y, storedColors, storedPositions, count,
+            shader, NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    env->ReleaseIntArrayElements(colorArray, const_cast<jint*>(colorValues), JNI_ABORT);
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
+static SkiaShader* SweepGradient_postCreate2(JNIEnv* env, jobject o, SkShader* shader,
+        float x, float y, int color0, int color1) {
+#ifdef USE_OPENGL_RENDERER
+    float* storedPositions = new float[2];
+    storedPositions[0] = 0.0f;
+    storedPositions[1] = 1.0f;
+
+    uint32_t* storedColors = new uint32_t[2];
+    storedColors[0] = static_cast<uint32_t>(color0);
+    storedColors[1] = static_cast<uint32_t>(color1);
+
+    SkiaShader* skiaShader = new SkiaSweepGradientShader(x, y, storedColors, storedPositions, 2,
+            shader, NULL, (shader->getFlags() & SkShader::kOpaqueAlpha_Flag) == 0);
+
+    return skiaShader;
+#else
+    return NULL;
+#endif
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
 static SkShader* ComposeShader_create1(JNIEnv* env, jobject o,
@@ -389,8 +442,10 @@
 };
 
 static JNINativeMethod gSweepGradientMethods[] = {
-    {"nativeCreate1",   "(FF[I[F)I",  (void*)SweepGradient_create1   },
-    {"nativeCreate2",   "(FFII)I",    (void*)SweepGradient_create2   }
+    {"nativeCreate1",      "(FF[I[F)I",  (void*)SweepGradient_create1     },
+    {"nativeCreate2",      "(FFII)I",    (void*)SweepGradient_create2     },
+    { "nativePostCreate1", "(IFF[I[F)I", (void*)SweepGradient_postCreate1 },
+    { "nativePostCreate2", "(IFFII)I",   (void*)SweepGradient_postCreate2 }
 };
 
 static JNINativeMethod gComposeShaderMethods[] = {
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index b397662..0400b5c 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -71,7 +71,8 @@
      * @param localM The shader's new local matrix, or null to specify identity
      */
     public void setLocalMatrix(Matrix localM) {
-        nativeSetLocalMatrix(native_instance, native_shader, localM.native_instance);
+        nativeSetLocalMatrix(native_instance, native_shader,
+                localM == null ? 0 : localM.native_instance);
     }
 
     protected void finalize() throws Throwable {
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 7456993..2afdd4d 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -42,6 +42,7 @@
                         "color and position arrays must be of equal length");
         }
         native_instance = nativeCreate1(cx, cy, colors, positions);
+        native_shader = nativePostCreate1(native_instance, cx, cy, colors, positions);
     }
 
     /**
@@ -54,11 +55,15 @@
      */
     public SweepGradient(float cx, float cy, int color0, int color1) {
         native_instance = nativeCreate2(cx, cy, color0, color1);
+        native_shader = nativePostCreate2(native_instance, cx, cy, color0, color1);
     }
 
-    private static native int nativeCreate1(float x, float y,
-                                            int colors[], float positions[]);
-    private static native int nativeCreate2(float x, float y,
-                                            int color0, int color1);
+    private static native int nativeCreate1(float x, float y, int colors[], float positions[]);
+    private static native int nativeCreate2(float x, float y, int color0, int color1);
+
+    private static native int nativePostCreate1(int native_shader, float cx, float cy,
+            int[] colors, float[] positions);    
+    private static native int nativePostCreate2(int native_shader, float cx, float cy,
+            int color0, int color1);
 }
 
diff --git a/libs/hwui/GradientCache.cpp b/libs/hwui/GradientCache.cpp
index 9957370..97f4cb4 100644
--- a/libs/hwui/GradientCache.cpp
+++ b/libs/hwui/GradientCache.cpp
@@ -116,7 +116,7 @@
     mCache.clear();
 }
 
-Texture* GradientCache::addLinearGradient(SkShader* shader, float* bounds, uint32_t* colors,
+Texture* GradientCache::addLinearGradient(SkShader* shader, uint32_t* colors,
         float* positions, int count, SkShader::TileMode tileMode) {
     SkBitmap bitmap;
     bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1024, 1);
diff --git a/libs/hwui/GradientCache.h b/libs/hwui/GradientCache.h
index 51a8c01..c829fd4 100644
--- a/libs/hwui/GradientCache.h
+++ b/libs/hwui/GradientCache.h
@@ -46,8 +46,8 @@
      * Adds a new linear gradient to the cache. The generated texture is
      * returned.
      */
-    Texture* addLinearGradient(SkShader* shader, float* bounds, uint32_t* colors,
-            float* positions, int count, SkShader::TileMode tileMode);
+    Texture* addLinearGradient(SkShader* shader, uint32_t* colors, float* positions,
+            int count, SkShader::TileMode tileMode = SkShader::kClamp_TileMode);
     /**
      * Returns the texture associated with the specified shader.
      */
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index becbc22..bcc1edf 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -33,11 +33,18 @@
         "attribute vec2 texCoords;\n";
 const char* gVS_Header_Uniforms =
         "uniform mat4 transform;\n";
-const char* gVS_Header_Uniforms_HasGradient =
+const char* gVS_Header_Uniforms_HasGradient[3] = {
+        // Linear
         "uniform float gradientLength;\n"
         "uniform vec2 gradient;\n"
         "uniform vec2 gradientStart;\n"
-        "uniform mat4 screenSpace;\n";
+        "uniform mat4 screenSpace;\n",
+        // Circular
+        "",
+        // Sweep
+        "uniform vec2 gradientStart;\n"
+        "uniform mat4 screenSpace;\n"
+};
 const char* gVS_Header_Uniforms_HasBitmap =
         "uniform mat4 textureTransform;\n"
         "uniform vec2 textureDimension;\n";
@@ -45,15 +52,28 @@
         "varying vec2 outTexCoords;\n";
 const char* gVS_Header_Varyings_HasBitmap =
         "varying vec2 outBitmapTexCoords;\n";
-const char* gVS_Header_Varyings_HasGradient =
-        "varying float index;\n";
+const char* gVS_Header_Varyings_HasGradient[3] = {
+        // Linear
+        "varying float index;\n",
+        // Circular
+        "",
+        // Sweep
+        "varying vec2 sweep;\n"
+};
 const char* gVS_Main =
         "\nvoid main(void) {\n";
 const char* gVS_Main_OutTexCoords =
         "    outTexCoords = texCoords;\n";
-const char* gVS_Main_OutGradientIndex =
+const char* gVS_Main_OutGradient[3] = {
+        // Linear
         "    vec4 location = screenSpace * position;\n"
-        "    index = dot(location.xy - gradientStart, gradient) * gradientLength;\n";
+        "    index = dot(location.xy - gradientStart, gradient) * gradientLength;\n",
+        // Circular
+        "",
+        // Sweep
+        "    vec4 location = screenSpace * position;\n"
+        "    sweep = location.xy - gradientStart;\n"
+};
 const char* gVS_Main_OutBitmapTexCoords =
         "    vec4 bitmapCoords = textureTransform * position;\n"
         "    outBitmapTexCoords = bitmapCoords.xy * textureDimension;\n";
@@ -74,8 +94,14 @@
         "uniform vec4 color;\n";
 const char* gFS_Uniforms_TextureSampler =
         "uniform sampler2D sampler;\n";
-const char* gFS_Uniforms_GradientSampler =
-        "uniform sampler2D gradientSampler;\n";
+const char* gFS_Uniforms_GradientSampler[3] = {
+        // Linear
+        "uniform sampler2D gradientSampler;\n",
+        // Circular
+        "uniform sampler2D gradientSampler;\n",
+        // Sweep
+        "uniform sampler2D gradientSampler;\n"
+};
 const char* gFS_Uniforms_BitmapSampler =
         "uniform sampler2D bitmapSampler;\n";
 const char* gFS_Uniforms_ColorOp[4] = {
@@ -99,8 +125,15 @@
         "    fragColor = color * texture2D(sampler, outTexCoords);\n";
 const char* gFS_Main_FetchA8Texture =
         "    fragColor = color * texture2D(sampler, outTexCoords).a;\n";
-const char* gFS_Main_FetchGradient =
-        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n";
+const char* gFS_Main_FetchGradient[3] = {
+        // Linear
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index, 0.5));\n",
+        // Circular
+        "",
+        // Sweep
+        "    float index = atan(sweep.y, sweep.x) * 0.15915494309; // inv(2 * PI)\n"
+        "    vec4 gradientColor = texture2D(gradientSampler, vec2(index - floor(index), 0.5));\n"
+};
 const char* gFS_Main_FetchBitmap =
         "    vec4 bitmapColor = texture2D(bitmapSampler, outBitmapTexCoords);\n";
 const char* gFS_Main_FetchBitmapNpot =
@@ -217,7 +250,7 @@
     ssize_t index = mCache.indexOfKey(key);
     Program* program = NULL;
     if (index < 0) {
-        PROGRAM_LOGD("Could not find program with key 0x%x", key);
+        description.log("Could not find program");
         program = generateProgram(description, key);
         mCache.add(key, program);
     } else {
@@ -247,7 +280,7 @@
     // Uniforms
     shader.append(gVS_Header_Uniforms);
     if (description.hasGradient) {
-        shader.append(gVS_Header_Uniforms_HasGradient);
+        shader.append(gVS_Header_Uniforms_HasGradient[description.gradientType]);
     }
     if (description.hasBitmap) {
         shader.append(gVS_Header_Uniforms_HasBitmap);
@@ -257,7 +290,7 @@
         shader.append(gVS_Header_Varyings_HasTexture);
     }
     if (description.hasGradient) {
-        shader.append(gVS_Header_Varyings_HasGradient);
+        shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
     }
     if (description.hasBitmap) {
         shader.append(gVS_Header_Varyings_HasBitmap);
@@ -269,7 +302,7 @@
             shader.append(gVS_Main_OutTexCoords);
         }
         if (description.hasGradient) {
-            shader.append(gVS_Main_OutGradientIndex);
+            shader.append(gVS_Main_OutGradient[description.gradientType]);
         }
         if (description.hasBitmap) {
             shader.append(gVS_Main_OutBitmapTexCoords);
@@ -301,7 +334,7 @@
         shader.append(gVS_Header_Varyings_HasTexture);
     }
     if (description.hasGradient) {
-        shader.append(gVS_Header_Varyings_HasGradient);
+        shader.append(gVS_Header_Varyings_HasGradient[description.gradientType]);
     }
     if (description.hasBitmap) {
         shader.append(gVS_Header_Varyings_HasBitmap);
@@ -314,7 +347,7 @@
         shader.append(gFS_Uniforms_TextureSampler);
     }
     if (description.hasGradient) {
-        shader.append(gFS_Uniforms_GradientSampler);
+        shader.append(gFS_Uniforms_GradientSampler[description.gradientType]);
     }
     if (description.hasBitmap) {
         shader.append(gFS_Uniforms_BitmapSampler);
@@ -348,7 +381,7 @@
             shader.append(gFS_Main_FetchColor);
         }
         if (description.hasGradient) {
-            shader.append(gFS_Main_FetchGradient);
+            shader.append(gFS_Main_FetchGradient[description.gradientType]);
         }
         if (description.hasBitmap) {
             if (!description.isBitmapNpot) {
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index 0a17052..4fa8011 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -44,9 +44,6 @@
     #define PROGRAM_LOGD(...)
 #endif
 
-/*
- * IMPORTANT: All 32 bits are used, switch to a long.
- */
 #define PROGRAM_KEY_TEXTURE 0x1
 #define PROGRAM_KEY_A8_TEXTURE 0x2
 #define PROGRAM_KEY_BITMAP 0x4
@@ -70,14 +67,13 @@
 #define PROGRAM_BITMAP_WRAPS_SHIFT 9
 #define PROGRAM_BITMAP_WRAPT_SHIFT 11
 
+#define PROGRAM_GRADIENT_TYPE_SHIFT 33
+
 ///////////////////////////////////////////////////////////////////////////////
 // Types
 ///////////////////////////////////////////////////////////////////////////////
 
-/*
- * IMPORTANT: All 32 bits are used, switch to a long.
- */
-typedef uint32_t programid;
+typedef uint64_t programid;
 
 ///////////////////////////////////////////////////////////////////////////////
 // Cache
@@ -96,9 +92,16 @@
         kColorBlend
     };
 
+    enum Gradient {
+        kGradientLinear,
+        kGradientCircular,
+        kGradientSweep
+    };
+
     ProgramDescription():
         hasTexture(false), hasAlpha8Texture(false),
         hasBitmap(false), isBitmapNpot(false), hasGradient(false),
+        gradientType(kGradientLinear),
         shadersMode(SkXfermode::kClear_Mode), isBitmapFirst(false),
         bitmapWrapS(GL_CLAMP_TO_EDGE), bitmapWrapT(GL_CLAMP_TO_EDGE),
         colorOp(kColorNone), colorMode(SkXfermode::kClear_Mode),
@@ -112,8 +115,12 @@
     // Shaders
     bool hasBitmap;
     bool isBitmapNpot;
+
     bool hasGradient;
+    Gradient gradientType;
+
     SkXfermode::Mode shadersMode;
+
     bool isBitmapFirst;
     GLenum bitmapWrapS;
     GLenum bitmapWrapT;
@@ -152,7 +159,8 @@
             }
         }
         if (hasGradient) key |= PROGRAM_KEY_GRADIENT;
-        if (isBitmapFirst) key  |= PROGRAM_KEY_BITMAP_FIRST;
+        key |= programid(gradientType) << PROGRAM_GRADIENT_TYPE_SHIFT;
+        if (isBitmapFirst) key |= PROGRAM_KEY_BITMAP_FIRST;
         if (hasBitmap && hasGradient) {
             key |= (shadersMode & PROGRAM_MAX_XFERMODE) << PROGRAM_XFERMODE_SHADER_SHIFT;
         }
@@ -174,6 +182,12 @@
         if (swapSrcDst) key |= PROGRAM_KEY_SWAP_SRC_DST;
         return key;
     }
+
+    void log(const char* message) const {
+        programid k = key();
+        PROGRAM_LOGD("%s (key = 0x%.8x%.8x)", message, uint32_t(k >> 32),
+                uint32_t(k & 0xffffffff));
+    }
 }; // struct ProgramDescription
 
 /**
diff --git a/libs/hwui/SkiaShader.cpp b/libs/hwui/SkiaShader.cpp
index 946cc4b..c5d9767 100644
--- a/libs/hwui/SkiaShader.cpp
+++ b/libs/hwui/SkiaShader.cpp
@@ -164,6 +164,7 @@
 void SkiaLinearGradientShader::describe(ProgramDescription& description,
         const Extensions& extensions) {
     description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientLinear;
 }
 
 void SkiaLinearGradientShader::setupProgram(Program* program, const mat4& modelView,
@@ -173,8 +174,7 @@
 
     Texture* texture = mGradientCache->get(mKey);
     if (!texture) {
-        texture = mGradientCache->addLinearGradient(mKey, mBounds, mColors, mPositions,
-                mCount, mTileX);
+        texture = mGradientCache->addLinearGradient(mKey, mColors, mPositions, mCount, mTileX);
     }
 
     Rect start(mBounds[0], mBounds[1], mBounds[2], mBounds[3]);
@@ -209,6 +209,64 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Sweep gradient shader
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaSweepGradientShader::SkiaSweepGradientShader(float x, float y, uint32_t* colors,
+        float* positions, int count, SkShader* key, SkMatrix* matrix, bool blend):
+        SkiaShader(kSweepGradient, key, SkShader::kClamp_TileMode,
+                SkShader::kClamp_TileMode, matrix, blend),
+        mX(x), mY(y), mColors(colors), mPositions(positions), mCount(count) {
+}
+
+SkiaSweepGradientShader::~SkiaSweepGradientShader() {
+    delete[] mColors;
+    delete[] mPositions;
+}
+
+void SkiaSweepGradientShader::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.hasGradient = true;
+    description.gradientType = ProgramDescription::kGradientSweep;
+}
+
+void SkiaSweepGradientShader::setupProgram(Program* program, const mat4& modelView,
+        const Snapshot& snapshot, GLuint* textureUnit) {
+    GLuint textureSlot = (*textureUnit)++;
+    glActiveTexture(gTextureUnitsMap[textureSlot]);
+
+    Texture* texture = mGradientCache->get(mKey);
+    if (!texture) {
+        texture = mGradientCache->addLinearGradient(mKey, mColors, mPositions, mCount);
+    }
+
+    float left = mX;
+    float top = mY;
+
+    if (mMatrix) {
+        mat4 shaderMatrix(*mMatrix);
+        shaderMatrix.mapPoint(left, top);
+    }
+    snapshot.transform->mapPoint(left, top);
+
+    mat4 screenSpace(*snapshot.transform);
+    screenSpace.multiply(modelView);
+
+    // Uniforms
+    bindTexture(texture->id, gTileModes[mTileX], gTileModes[mTileY], textureSlot);
+    glUniform1i(program->getUniform("gradientSampler"), textureSlot);
+    glUniform2f(program->getUniform("gradientStart"), left, top);
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+void SkiaSweepGradientShader::updateTransforms(Program* program, const mat4& modelView,
+        const Snapshot& snapshot) {
+    mat4 screenSpace(*snapshot.transform);
+    screenSpace.multiply(modelView);
+    glUniformMatrix4fv(program->getUniform("screenSpace"), 1, GL_FALSE, &screenSpace.data[0]);
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Compose shader
 ///////////////////////////////////////////////////////////////////////////////
 
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index cc94ae6..b67bfee 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -145,6 +145,26 @@
 }; // struct SkiaLinearGradientShader
 
 /**
+ * A shader that draws a sweep gradient.
+ */
+struct SkiaSweepGradientShader: public SkiaShader {
+    SkiaSweepGradientShader(float x, float y, uint32_t* colors, float* positions, int count,
+            SkShader* key, SkMatrix* matrix, bool blend);
+    ~SkiaSweepGradientShader();
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
+            GLuint* textureUnit);
+    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);
+
+private:
+    float mX, mY;
+    uint32_t* mColors;
+    float* mPositions;
+    int mCount;
+}; // struct SkiaSweepGradientShader
+
+/**
  * A shader that draws two shaders, composited with an xfermode.
  */
 struct SkiaComposeShader: public SkiaShader {
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 8a08c48..af71a0f 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -31,6 +31,15 @@
         </activity>
         
         <activity
+                android:name="AdvancedGradientsActivity"
+                android:label="_Advanced Gradients">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
                 android:name="ResizeActivity"
                 android:label="_Resize"
                 android:windowSoftInputMode="adjustResize">
@@ -250,7 +259,7 @@
 
         <activity
                 android:name="AdvancedBlendActivity"
-                android:label="_AdvancedBlend">
+                android:label="_Advanced Blend">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
new file mode 100644
index 0000000..e232021
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/AdvancedGradientsActivity.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class AdvancedGradientsActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(new GradientsView(this));
+    }
+
+    static class GradientsView extends View {
+        private final Paint mPaint;
+        private final SweepGradient mSweepGradient;
+        private final RadialGradient mRadialGradient;
+        private final Matrix mMatrix;
+        private final Matrix mMatrix2;
+
+        GradientsView(Context c) {
+            super(c);
+
+            mSweepGradient = new SweepGradient(100.0f, 100.0f, 0xff000000, 0xffffffff);
+            mRadialGradient = new RadialGradient(100.0f, 100.0f, 100.0f, 0xff000000, 0xffffffff,
+                    Shader.TileMode.MIRROR);
+            
+            mMatrix = new Matrix();
+            mMatrix.setTranslate(50.0f, 50.0f);
+
+            mMatrix2 = new Matrix();
+            mMatrix2.setScale(2.0f, 2.0f);
+            
+            mPaint = new Paint();
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+            canvas.drawRGB(255, 255, 255);
+
+            // Bitmap shaders
+            canvas.save();
+            canvas.translate(130.0f, 100.0f);
+
+            mSweepGradient.setLocalMatrix(null);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mPaint.setShader(mRadialGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(400.0f, 000.0f);
+            
+            mSweepGradient.setLocalMatrix(mMatrix);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+
+            canvas.translate(-800.0f, 300.0f);
+            
+            mSweepGradient.setLocalMatrix(mMatrix2);
+            mPaint.setShader(mSweepGradient);
+            canvas.drawRect(0.0f, 0.0f, 200.0f, 200.0f, mPaint);
+            
+            
+            canvas.restore();
+        }
+    }
+}