Better backend for hardware layers.

With this new backend, a hardware layer is only recreated when
its associated view is udpated. This offers fast composition
in GL and fast update of the layer in GL as well.

Change-Id: I97c43a612f5955c6bf1c192c8ca4af10fdf1d076
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 04a521e..8c4e654 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -73,9 +73,21 @@
     // Constructors
     ///////////////////////////////////////////////////////////////////////////
 
+    /**
+     * Creates a canvas to render directly on screen.
+     */
     GLES20Canvas(boolean translucent) {
         this(false, translucent);
     }
+
+    /**
+     * Creates a canvas to render into an FBO.
+     */
+    GLES20Canvas(int fbo, boolean translucent) {
+        mOpaque = !translucent;
+        mRenderer = nCreateLayerRenderer(fbo);
+        setupFinalizer();
+    }
     
     protected GLES20Canvas(boolean record, boolean translucent) {
         mOpaque = !translucent;
@@ -89,7 +101,11 @@
         } else {
             mRenderer = nCreateRenderer();
         }
-       
+
+        setupFinalizer();
+    }
+
+    private void setupFinalizer() {
         if (mRenderer == 0) {
             throw new IllegalStateException("Could not create GLES20Canvas renderer");
         } else {
@@ -97,7 +113,8 @@
         }
     }
 
-    private native int nCreateRenderer();
+    private static native int nCreateRenderer();
+    private static native int nCreateLayerRenderer(int fbo);
     private static native int nGetDisplayListRenderer(int renderer);
     private static native void nDestroyRenderer(int renderer);
 
@@ -136,6 +153,15 @@
     }
 
     ///////////////////////////////////////////////////////////////////////////
+    // Hardware layers
+    ///////////////////////////////////////////////////////////////////////////
+    
+    static native int nCreateLayer(int width, int height, int[] layerInfo);
+    static native void nResizeLayer(int layerId, int layerTextureId, int width, int height,
+            int[] layerInfo);
+    static native void nDestroyLayer(int layerId, int layerTextureId);    
+    
+    ///////////////////////////////////////////////////////////////////////////
     // Canvas management
     ///////////////////////////////////////////////////////////////////////////
 
@@ -227,6 +253,34 @@
     private native void nDrawDisplayList(int renderer, int displayList);
 
     ///////////////////////////////////////////////////////////////////////////
+    // Hardware layer
+    ///////////////////////////////////////////////////////////////////////////
+    
+    void drawHardwareLayer(float left, float top, float right, float bottom,
+            HardwareLayer layer, Paint paint) {
+        final GLES20Layer glLayer = (GLES20Layer) layer;
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawLayer(mRenderer, left, top, right, bottom, glLayer.mLayerTextureId,
+                glLayer.getU(), glLayer.getV(), nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
+    }
+
+    private native void nDrawLayer(int renderer, float left, float top, float right, float bottom,
+            int layerTexture, float u, float v, int paint);
+    
+    void interrupt() {
+        nInterrupt(mRenderer);
+    }
+    
+    void resume() {
+        nResume(mRenderer);
+    }
+
+    private native void nInterrupt(int renderer);
+    private native void nResume(int renderer);
+
+    ///////////////////////////////////////////////////////////////////////////
     // Clipping
     ///////////////////////////////////////////////////////////////////////////
 
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index c548659..e813bc9 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -106,7 +106,11 @@
 
         @Override
         protected void finalize() throws Throwable {
-            replaceNativeObject(0);
+            try {
+                replaceNativeObject(0);
+            } finally {
+                super.finalize();
+            }
         }
     }
 }
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
new file mode 100644
index 0000000..336e07a
--- /dev/null
+++ b/core/java/android/view/GLES20Layer.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
+ */
+class GLES20Layer extends HardwareLayer {
+    private int mLayerId;
+    int mLayerTextureId;
+
+    private int mLayerWidth;
+    private int mLayerHeight;
+
+    private final GLES20Canvas mCanvas;
+
+    private float mU;
+    private float mV;
+
+    @SuppressWarnings({"FieldCanBeLocal", "UnusedDeclaration"})
+    private final Finalizer mFinalizer;
+
+    GLES20Layer(int width, int height, boolean isOpaque) {
+        super(width, height, isOpaque);
+
+        int[] layerInfo = new int[3];
+        mLayerId = GLES20Canvas.nCreateLayer(width, height, layerInfo);
+        if (mLayerId != 0) {
+            mLayerWidth = layerInfo[0];
+            mLayerHeight = layerInfo[1];
+            mLayerTextureId = layerInfo[2];
+
+            mCanvas = new GLES20Canvas(mLayerId, !isOpaque);
+            mFinalizer = new Finalizer(mLayerId, mLayerTextureId);
+            
+            mU = mWidth / (float) mLayerWidth;
+            mV = mHeight/ (float) mLayerHeight;
+        } else {
+            mCanvas = null;
+            mFinalizer = null;
+        }
+    }
+
+    float getU() {
+        return mU;
+    }
+
+    float getV() {
+        return mV;
+    }
+
+    @Override
+    boolean isValid() {
+        return mLayerId != 0 && mLayerWidth > 0 && mLayerHeight > 0;
+    }
+
+    @Override
+    void resize(int width, int height) {
+        if (!isValid() || width <= 0 || height <= 0) return;
+        if (width > mLayerWidth || height > mLayerHeight) {
+            mWidth = width;
+            mHeight = height;
+
+            int[] layerInfo = new int[3];
+
+            GLES20Canvas.nResizeLayer(mLayerId, mLayerTextureId, width, height, layerInfo);
+
+            mLayerWidth = layerInfo[0];
+            mLayerHeight = layerInfo[1];
+
+            mU = mWidth / (float) mLayerWidth;
+            mV = mHeight/ (float) mLayerHeight;            
+        }
+    }
+
+    @Override
+    HardwareCanvas getCanvas() {
+        return mCanvas;
+    }
+
+    @Override
+    void end(Canvas currentCanvas) {
+        if (currentCanvas instanceof GLES20Canvas) {
+            ((GLES20Canvas) currentCanvas).resume();
+        }
+    }
+
+    @Override
+    HardwareCanvas start(Canvas currentCanvas) {
+        if (currentCanvas instanceof GLES20Canvas) {
+            ((GLES20Canvas) currentCanvas).interrupt();
+        }
+        return getCanvas();
+    }
+
+    @Override
+    void destroy() {
+        mFinalizer.destroy();
+        mLayerId = mLayerTextureId = 0;
+    }
+
+    private static class Finalizer {
+        private int mLayerId;
+        private int mLayerTextureId;
+
+        public Finalizer(int layerId, int layerTextureId) {
+            mLayerId = layerId;
+            mLayerTextureId = layerTextureId;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            try {
+                if (mLayerId != 0 || mLayerTextureId != 0) {
+                    GLES20Canvas.nDestroyLayer(mLayerId, mLayerTextureId);
+                }
+            } finally {
+                super.finalize();
+            }
+        }
+
+        void destroy() {
+            GLES20Canvas.nDestroyLayer(mLayerId, mLayerTextureId);
+            mLayerId = mLayerTextureId = 0;
+        }
+    }
+}
diff --git a/core/java/android/view/GLES20RecordingCanvas.java b/core/java/android/view/GLES20RecordingCanvas.java
index 7f86d99..2603281 100644
--- a/core/java/android/view/GLES20RecordingCanvas.java
+++ b/core/java/android/view/GLES20RecordingCanvas.java
@@ -36,7 +36,8 @@
 class GLES20RecordingCanvas extends GLES20Canvas {
     // These lists ensure that any Bitmaps recorded by a DisplayList are kept alive as long
     // as the DisplayList is alive.
-    private HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
+    @SuppressWarnings({"MismatchedQueryAndUpdateOfCollection"})
+    private final HashSet<Bitmap> mBitmaps = new HashSet<Bitmap>();
 
     GLES20RecordingCanvas(boolean translucent) {
         super(true, translucent);
@@ -57,12 +58,6 @@
     }
 
     @Override
-    public void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
-            Paint paint) {
-        super.drawArc(oval, startAngle, sweepAngle, useCenter, paint);
-    }
-
-    @Override
     public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
         super.drawPatch(bitmap, chunks, dst, paint);
         mBitmaps.add(bitmap);
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 8b8d15e..1a5df98 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.Paint;
 
 /**
  * Hardware accelerated canvas. 
@@ -48,5 +49,18 @@
      * 
      * @param displayList The display list to replay.
      */
-    public abstract void drawDisplayList(DisplayList displayList);
+    abstract void drawDisplayList(DisplayList displayList);
+
+    /**
+     * Draws the specified layer onto this canvas.
+     *
+     * @param left The left coordinate of the layer
+     * @param top The top coordinate of the layer
+     * @param right The right coordinate of the layer
+     * @param bottom The bottom coordinate of the layer
+     * @param layer The layer to composite on this canvas
+     * @param paint The paint used to draw the layer
+     */
+    abstract void drawHardwareLayer(float left, float top, float right, float bottom,
+            HardwareLayer layer, Paint paint); 
 }
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
new file mode 100644
index 0000000..d01b8ce
--- /dev/null
+++ b/core/java/android/view/HardwareLayer.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 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 android.view;
+
+import android.graphics.Canvas;
+
+/**
+ * A hardware layer can be used to render graphics operations into a hardware
+ * friendly buffer. For instance, with an OpenGL backend, a hardware layer
+ * would use a Frame Buffer Object (FBO.) The hardware layer can be used as
+ * a drawing cache when a complex set of graphics operations needs to be
+ * drawn several times.
+ */
+abstract class HardwareLayer {
+    int mWidth;
+    int mHeight;
+
+    final boolean mOpaque;
+
+    /**
+     * Creates a new hardware layer at least as large as the supplied
+     * dimensions.
+     * 
+     * @param width The minimum width of the layer
+     * @param height The minimum height of the layer
+     * @param isOpaque Whether the layer should be opaque or not
+     */
+    HardwareLayer(int width, int height, boolean isOpaque) {
+        mWidth = width;
+        mHeight = height;
+        mOpaque = isOpaque;
+    }
+
+    /**
+     * Returns the minimum width of the layer.
+     * 
+     * @return The minimum desired width of the hardware layer 
+     */
+    int getWidth() {
+        return mWidth;
+    }
+
+    /**
+     * Returns the minimum height of the layer.
+     * 
+     * @return The minimum desired height of the hardware layer 
+     */
+    int getHeight() {
+        return mHeight;
+    }
+
+    /**
+     * Returns whether or not this layer is opaque.
+     * 
+     * @return True if the layer is opaque, false otherwise
+     */
+    boolean isOpaque() {
+        return mOpaque;
+    }
+
+    /**
+     * Indicates whether this layer can be rendered.
+     * 
+     * @return True if the layer can be rendered into, false otherwise
+     */
+    abstract boolean isValid();
+
+    /**
+     * Resizes the layer, if necessary, to be at least as large
+     * as the supplied dimensions.
+     * 
+     * @param width The new desired minimum width for this layer
+     * @param height The new desired minimum height for this layer
+     */
+    abstract void resize(int width, int height);
+
+    /**
+     * Returns a hardware canvas that can be used to render onto
+     * this layer.
+     * 
+     * @return A hardware canvas, or null if a canvas cannot be created
+     */
+    abstract HardwareCanvas getCanvas();
+
+    /**
+     * Destroys resources without waiting for a GC. 
+     */
+    abstract void destroy();
+
+    /**
+     * This must be invoked before drawing onto this layer.
+     * @param currentCanvas
+     */
+    abstract HardwareCanvas start(Canvas currentCanvas);
+    
+    /**
+     * This must be invoked after drawing onto this layer.
+     * @param currentCanvas
+     */
+    abstract void end(Canvas currentCanvas);
+}
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 63490ee..05a9ff8 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -116,6 +116,17 @@
     abstract DisplayList createDisplayList();
 
     /**
+     * Creates a new hardware layer.
+     * 
+     * @param width The minimum width of the layer
+     * @param height The minimum height of the layer
+     * @param isOpaque Whether the layer should be opaque or not
+     * 
+     * @return A hardware layer
+     */
+    abstract HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque);
+    
+    /**
      * Initializes the hardware renderer for the specified surface and setup the
      * renderer for drawing, if needed. This is invoked when the ViewRoot has
      * potentially lost the hardware renderer. The hardware renderer should be
@@ -682,6 +693,11 @@
         DisplayList createDisplayList() {
             return new GLES20DisplayList();
         }
+        
+        @Override
+        HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+            return new GLES20Layer(width, height, isOpaque);
+        }
 
         static HardwareRenderer create(boolean translucent) {
             if (GLES20Canvas.isAvailable()) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 32f0b14..703084f 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2049,6 +2049,7 @@
     private Bitmap mDrawingCache;
     private Bitmap mUnscaledDrawingCache;
     private DisplayList mDisplayList;
+    private HardwareLayer mHardwareLayer;
 
     /**
      * When this view has focus and the next focus is {@link #FOCUS_LEFT},
@@ -2204,6 +2205,10 @@
      * 
      * <p>A hardware layer is useful to apply a specific color filter and/or
      * blending mode and/or translucency to a view and all its children.</p>
+     * <p>A hardware layer can be used to cache a complex view tree into a
+     * texture and reduce the complexity of drawing operations. For instance,
+     * when animating a complex view tree with a translation, a hardware layer can
+     * be used to render the view tree only once.</p>
      * <p>A hardware layer can also be used to increase the rendering quality when
      * rotation transformations are applied on a view. It can also be used to
      * prevent potential clipping issues when applying 3D transforms on a view.</p>
@@ -7551,9 +7556,16 @@
      */
     protected void onDetachedFromWindow() {
         mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
+
         removeUnsetPressCallback();
         removeLongPressCallback();
+
         destroyDrawingCache();
+
+        if (mHardwareLayer != null) {
+            mHardwareLayer.destroy();
+            mHardwareLayer = null;
+        }
     }
 
     /**
@@ -7868,23 +7880,36 @@
             throw new IllegalArgumentException("Layer type can only be one of: LAYER_TYPE_NONE, " 
                     + "LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE");
         }
+        
+        if (layerType == mLayerType) return;
 
         // Destroy any previous software drawing cache if needed
-        if (mLayerType == LAYER_TYPE_SOFTWARE && layerType != LAYER_TYPE_SOFTWARE) {
-            if (mDrawingCache != null) {
-                mDrawingCache.recycle();
-                mDrawingCache = null;
-            }
-
-            if (mUnscaledDrawingCache != null) {
-                mUnscaledDrawingCache.recycle();
-                mUnscaledDrawingCache = null;
-            }
+        switch (mLayerType) {
+            case LAYER_TYPE_SOFTWARE:
+                if (mDrawingCache != null) {
+                    mDrawingCache.recycle();
+                    mDrawingCache = null;
+                }
+    
+                if (mUnscaledDrawingCache != null) {
+                    mUnscaledDrawingCache.recycle();
+                    mUnscaledDrawingCache = null;
+                }
+                break;
+            case LAYER_TYPE_HARDWARE:
+                if (mHardwareLayer != null) {
+                    mHardwareLayer.destroy();
+                    mHardwareLayer = null;
+                }
+                break;
+            default:
+                break;
         }
 
         mLayerType = layerType;
         mLayerPaint = mLayerType == LAYER_TYPE_NONE ? null : paint;
 
+        // TODO: Make sure we invalidate the parent's display list
         invalidate();
     }
 
@@ -7905,6 +7930,62 @@
     public int getLayerType() {
         return mLayerType;
     }
+    
+    /**
+     * <p>Returns a hardware layer that can be used to draw this view again
+     * without executing its draw method.</p>
+     *
+     * @return A HardwareLayer ready to render, or null if an error occurred.
+     */
+    HardwareLayer getHardwareLayer(Canvas currentCanvas) {
+        if (mAttachInfo == null || mAttachInfo.mHardwareRenderer == null) {
+            return null;
+        }
+
+        final int width = mRight - mLeft;
+        final int height = mBottom - mTop;
+        
+        if (width == 0 || height == 0) {
+            return null;
+        }
+
+        if ((mPrivateFlags & DRAWING_CACHE_VALID) == 0 || mHardwareLayer == null) {
+            if (mHardwareLayer == null) {
+                mHardwareLayer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+                        width, height, isOpaque());
+            } else if (mHardwareLayer.getWidth() != width || mHardwareLayer.getHeight() != height) {
+                mHardwareLayer.resize(width, height);
+            }
+
+            final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas);
+            try {
+                canvas.setViewport(width, height);
+                canvas.onPreDraw();
+
+                computeScroll();
+                canvas.translate(-mScrollX, -mScrollY);
+
+                final int restoreCount = canvas.save();
+
+                mPrivateFlags |= DRAWN | DRAWING_CACHE_VALID;
+    
+                // Fast path for layouts with no backgrounds
+                if ((mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                    mPrivateFlags &= ~DIRTY_MASK;
+                    dispatchDraw(canvas);
+                } else {
+                    draw(canvas);
+                }
+    
+                canvas.restoreToCount(restoreCount);
+            } finally {
+                canvas.onPostDraw();
+                mHardwareLayer.end(currentCanvas);
+            }
+        }
+
+        return mHardwareLayer;
+    }
 
     /**
      * <p>Enables or disables the drawing cache. When the drawing cache is enabled, the next call
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 7af611e..ed5eed1 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2179,7 +2179,7 @@
 
         boolean scalingRequired = false;
         boolean caching;
-        final int layerType = child.getLayerType();
+        int layerType = child.getLayerType();
         
         if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
                 (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
@@ -2278,6 +2278,7 @@
         if (caching) {
             if (!canvas.isHardwareAccelerated()) {
                 if (layerType != LAYER_TYPE_NONE) {
+                    layerType = LAYER_TYPE_SOFTWARE;
                     child.buildDrawingCache(true);
                 }
                 cache = child.getDrawingCache(true);
@@ -2354,13 +2355,11 @@
                         }
                         if (layerType != LAYER_TYPE_NONE && child.mLayerPaint != null) {
                             child.mLayerPaint.setAlpha(multipliedAlpha);
-                            canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct,
-                                    child.mLayerPaint, layerFlags);
                         } else {
                             canvas.saveLayerAlpha(sx, sy, sx + cr - cl, sy + cb - ct,
                                     multipliedAlpha, layerFlags);
+                            layerSaved = true;
                         }
-                        layerSaved = true;
                     } else {
                         // Alpha is handled by the child directly, clobber the layer's alpha
                         if (layerType != LAYER_TYPE_NONE && child.mLayerPaint != null) {
@@ -2388,24 +2387,35 @@
         }
 
         if (hasNoCache) {
+            boolean layerRendered = false;
             if (!layerSaved && layerType == LAYER_TYPE_HARDWARE) {
-                canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct, child.mLayerPaint,
-                        Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
-            }
-            if (!hasDisplayList) {
-                // Fast path for layouts with no backgrounds
-                if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
-                    if (ViewDebug.TRACE_HIERARCHY) {
-                        ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
-                    }
-                    child.mPrivateFlags &= ~DIRTY_MASK;
-                    child.dispatchDraw(canvas);
+                final HardwareLayer layer = child.getHardwareLayer(canvas);
+                if (layer != null && layer.isValid()) {
+                    ((HardwareCanvas) canvas).drawHardwareLayer(0, 0, cr - cl, cb - ct,
+                            layer, child.mLayerPaint);
+                    layerRendered = true;
                 } else {
-                    child.draw(canvas);
+                    canvas.saveLayer(sx, sy, sx + cr - cl, sy + cb - ct, child.mLayerPaint,
+                            Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.CLIP_TO_LAYER_SAVE_FLAG);
                 }
-            } else {
-                child.mPrivateFlags &= ~DIRTY_MASK;
-                ((HardwareCanvas) canvas).drawDisplayList(displayList);
+            }
+
+            if (!layerRendered) {
+                if (!hasDisplayList) {
+                    // Fast path for layouts with no backgrounds
+                    if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
+                        if (ViewDebug.TRACE_HIERARCHY) {
+                            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
+                        }
+                        child.mPrivateFlags &= ~DIRTY_MASK;
+                        child.dispatchDraw(canvas);
+                    } else {
+                        child.draw(canvas);
+                    }
+                } else {
+                    child.mPrivateFlags &= ~DIRTY_MASK;
+                    ((HardwareCanvas) canvas).drawDisplayList(displayList);
+                }
             }
         } else if (cache != null) {
             child.mPrivateFlags &= ~DIRTY_MASK;
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index b4c868f..d6991e6 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -32,6 +32,7 @@
 #include <SkXfermode.h>
 
 #include <DisplayListRenderer.h>
+#include <LayerRenderer.h>
 #include <OpenGLDebugRenderer.h>
 #include <OpenGLRenderer.h>
 #include <SkiaShader.h>
@@ -77,7 +78,7 @@
 // Constructors
 // ----------------------------------------------------------------------------
 
-static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject canvas) {
+static OpenGLRenderer* android_view_GLES20Canvas_createRenderer(JNIEnv* env, jobject clazz) {
     RENDERER_LOGD("Create OpenGLRenderer");
 #if PROFILE_RENDERER
     return new OpenGLDebugRenderer;
@@ -442,6 +443,71 @@
     renderer->drawDisplayList(displayList);
 }
 
+// ----------------------------------------------------------------------------
+// Layers
+// ----------------------------------------------------------------------------
+
+static void android_view_GLES20Canvas_interrupt(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->interrupt();
+}
+
+static void android_view_GLES20Canvas_resume(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer) {
+    renderer->resume();
+}
+
+static OpenGLRenderer* android_view_GLES20Canvas_createLayerRenderer(JNIEnv* env,
+        jobject clazz, jint fbo) {
+    return new LayerRenderer(fbo);
+}
+
+static jint android_view_GLES20Canvas_createLayer(JNIEnv* env,
+        jobject clazz, jint width, jint height, jintArray layerInfo) {
+    uint32_t layerWidth = 0;
+    uint32_t layerHeight = 0;
+    GLuint textureId = 0;
+
+    jint layerId = LayerRenderer::createLayer(width, height,
+            &layerWidth, &layerHeight, &textureId);
+
+    if (layerId) {
+        jint* storage = env->GetIntArrayElements(layerInfo, NULL);
+        storage[0] = layerWidth;
+        storage[1] = layerHeight;
+        storage[2] = textureId;
+        env->ReleaseIntArrayElements(layerInfo, storage, 0);
+    }
+
+    return layerId;
+}
+
+static void android_view_GLES20Canvas_resizeLayer(JNIEnv* env,
+        jobject clazz, jint layerId, jint layerTextureId, jint width, jint height,
+        jintArray layerInfo) {
+    uint32_t layerWidth = 0;
+    uint32_t layerHeight = 0;
+
+    LayerRenderer::resizeLayer(layerId, layerTextureId, width, height, &layerWidth, &layerHeight);
+
+    jint* storage = env->GetIntArrayElements(layerInfo, NULL);
+    storage[0] = layerWidth;
+    storage[1] = layerHeight;
+    env->ReleaseIntArrayElements(layerInfo, storage, 0);
+}
+
+static void android_view_GLES20Canvas_destroyLayer(JNIEnv* env,
+        jobject clazz, jint layerId, jint layerTextureId) {
+    LayerRenderer::destroyLayer(layerId, layerTextureId);
+}
+
+static void android_view_GLES20Canvas_drawLayer(JNIEnv* env,
+        jobject canvas, OpenGLRenderer* renderer,
+        jfloat left, jfloat top, jfloat right, jfloat bottom,
+        jint layerTexture, jfloat u, jfloat v, SkPaint* paint) {
+    renderer->drawLayer(layerTexture, left, top, right, bottom, u, v, paint);
+}
+
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -522,10 +588,20 @@
     { "nGetClipBounds",     "(ILandroid/graphics/Rect;)Z",
             (void*) android_view_GLES20Canvas_getClipBounds },
 
-    { "nGetDisplayList",  "(I)I",           (void*) android_view_GLES20Canvas_getDisplayList },
-    { "nDestroyDisplayList", "(I)V",           (void*) android_view_GLES20Canvas_destroyDisplayList },
-    { "nGetDisplayListRenderer", "(I)I",     (void*) android_view_GLES20Canvas_getDisplayListRenderer },
-    { "nDrawDisplayList",    "(II)V",          (void*) android_view_GLES20Canvas_drawDisplayList },
+    { "nGetDisplayList",         "(I)I",       (void*) android_view_GLES20Canvas_getDisplayList },
+    { "nDestroyDisplayList",     "(I)V",       (void*) android_view_GLES20Canvas_destroyDisplayList },
+    { "nGetDisplayListRenderer", "(I)I",       (void*) android_view_GLES20Canvas_getDisplayListRenderer },
+    { "nDrawDisplayList",        "(II)V",      (void*) android_view_GLES20Canvas_drawDisplayList },
+
+    { "nInterrupt",              "(I)V",        (void*) android_view_GLES20Canvas_interrupt },
+    { "nResume",                 "(I)V",        (void*) android_view_GLES20Canvas_resume },
+
+    { "nCreateLayerRenderer",    "(I)I",       (void*) android_view_GLES20Canvas_createLayerRenderer },
+    { "nCreateLayer",            "(II[I)I",    (void*) android_view_GLES20Canvas_createLayer },
+    { "nResizeLayer",            "(IIII[I)V",  (void*) android_view_GLES20Canvas_resizeLayer },
+    { "nDestroyLayer",           "(II)V",      (void*) android_view_GLES20Canvas_destroyLayer },
+    { "nDrawLayer",              "(IFFFFIFFI)V",
+            (void*) android_view_GLES20Canvas_drawLayer },
 
 #endif
 };
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 29158e5..c49be93 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -13,6 +13,7 @@
 		FboCache.cpp \
 		GradientCache.cpp \
 		LayerCache.cpp \
+		LayerRenderer.cpp \
 		Matrix.cpp \
 		OpenGLDebugRenderer.cpp \
 		OpenGLRenderer.cpp \
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index afb82bf..fdb4e8c 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -241,6 +241,11 @@
                 renderer.drawDisplayList(getDisplayList());
             }
             break;
+            case DrawLayer: {
+                renderer.drawLayer(getInt(), getFloat(), getFloat(), getFloat(), getFloat(),
+                        getFloat(), getFloat(), getPaint());
+            }
+            break;
             case DrawBitmap: {
                 renderer.drawBitmap(getBitmap(), getFloat(), getFloat(), getPaint());
             }
@@ -483,6 +488,16 @@
     addDisplayList(displayList);
 }
 
+void DisplayListRenderer::drawLayer(int texture, float left, float top, float right, float bottom,
+        float u, float v, SkPaint* paint) {
+    addOp(DisplayList::DrawLayer);
+    addInt(texture);
+    addBounds(left, top, right, bottom);
+    addFloat(u);
+    addFloat(v);
+    addPaint(paint);
+}
+
 void DisplayListRenderer::drawBitmap(SkBitmap* bitmap, float left, float top,
         SkPaint* paint) {
     addOp(DisplayList::DrawBitmap);
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index fedb174..62cb0e8 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -93,6 +93,7 @@
         ConcatMatrix,
         ClipRect,
         DrawDisplayList,
+        DrawLayer,
         DrawBitmap,
         DrawBitmapMatrix,
         DrawBitmapRect,
@@ -245,6 +246,8 @@
     bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
     void drawDisplayList(DisplayList* displayList);
+    void drawLayer(int texture, float left, float top, float right, float bottom,
+            float u, float v, SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
new file mode 100644
index 0000000..3583b86
--- /dev/null
+++ b/libs/hwui/LayerRenderer.cpp
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 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 "LayerRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Rendering
+///////////////////////////////////////////////////////////////////////////////
+
+void LayerRenderer::prepare(bool opaque) {
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &mPreviousFbo);
+    glBindFramebuffer(GL_FRAMEBUFFER, mFbo);
+    OpenGLRenderer::prepare(opaque);
+}
+
+void LayerRenderer::finish() {
+    OpenGLRenderer::finish();
+    glBindFramebuffer(GL_FRAMEBUFFER, mPreviousFbo);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Static functions
+///////////////////////////////////////////////////////////////////////////////
+
+GLuint LayerRenderer::createLayer(uint32_t width, uint32_t height,
+        uint32_t* layerWidth, uint32_t* layerHeight, GLuint* texture) {
+    GLuint previousFbo;
+    glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
+
+    GLuint fbo = 0;
+    glGenFramebuffers(1, &fbo);
+    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+    glActiveTexture(GL_TEXTURE0);
+    glGenTextures(1, texture);
+    glBindTexture(GL_TEXTURE_2D, *texture);
+
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+    if (glGetError() != GL_NO_ERROR) {
+        glDeleteBuffers(1, &fbo);
+        glDeleteTextures(1, texture);
+        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+        return 0;
+    }
+
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                *texture, 0);
+
+    if (glGetError() != GL_NO_ERROR) {
+        glDeleteBuffers(1, &fbo);
+        glDeleteTextures(1, texture);
+        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+        return 0;
+    }
+
+    glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+    *layerWidth = width;
+    *layerHeight = height;
+
+    return fbo;
+}
+
+void LayerRenderer::resizeLayer(GLuint fbo, GLuint texture, uint32_t width, uint32_t height,
+        uint32_t* layerWidth, uint32_t* layerHeight) {
+    glActiveTexture(GL_TEXTURE0);
+    glBindTexture(GL_TEXTURE_2D, texture);
+
+    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
+            GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+
+    if (glGetError() != GL_NO_ERROR) {
+        glDeleteBuffers(1, &fbo);
+        glDeleteTextures(1, texture);
+        glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
+
+        *layerWidth = 0;
+        *layerHeight = 0;
+
+        return;
+    }
+
+    *layerWidth = width;
+    *layerHeight = height;
+}
+
+void LayerRenderer::destroyLayer(GLuint fbo, GLuint texture) {
+    if (fbo) glDeleteFramebuffers(1, &fbo);
+    if (texture) glDeleteTextures(1, &texture);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/LayerRenderer.h b/libs/hwui/LayerRenderer.h
new file mode 100644
index 0000000..a8f1ff7
--- /dev/null
+++ b/libs/hwui/LayerRenderer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2011 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_HWUI_LAYER_RENDERER_H
+#define ANDROID_HWUI_LAYER_RENDERER_H
+
+#include "OpenGLRenderer.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Renderer
+///////////////////////////////////////////////////////////////////////////////
+
+class LayerRenderer: public OpenGLRenderer {
+public:
+    LayerRenderer(GLuint fbo): mFbo(fbo) {
+    }
+
+    ~LayerRenderer() {
+    }
+
+    void prepare(bool opaque);
+    void finish();
+
+    static GLuint createLayer(uint32_t width, uint32_t height,
+            uint32_t* layerWidth, uint32_t* layerHeight, GLuint* texture);
+    static void resizeLayer(GLuint fbo, GLuint texture, uint32_t width, uint32_t height,
+            uint32_t* layerWidth, uint32_t* layerHeight);
+    static void destroyLayer(GLuint fbo, GLuint texture);
+
+private:
+    GLuint mFbo;
+    GLuint mPreviousFbo;
+
+}; // class LayerRenderer
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_HWUI_LAYER_RENDERER_H
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 99bb6f0..aefefe4 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -178,7 +178,7 @@
 #endif
 }
 
-void OpenGLRenderer::acquireContext() {
+void OpenGLRenderer::interrupt() {
     if (mCaches.currentProgram) {
         if (mCaches.currentProgram->isInUse()) {
             mCaches.currentProgram->remove();
@@ -188,7 +188,11 @@
     mCaches.unbindMeshBuffer();
 }
 
-void OpenGLRenderer::releaseContext() {
+void OpenGLRenderer::acquireContext() {
+    interrupt();
+}
+
+void OpenGLRenderer::resume() {
     glViewport(0, 0, mSnapshot->viewport.getWidth(), mSnapshot->viewport.getHeight());
 
     glEnable(GL_SCISSOR_TEST);
@@ -205,6 +209,10 @@
     glBlendEquation(GL_FUNC_ADD);
 }
 
+void OpenGLRenderer::releaseContext() {
+    resume();
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // State management
 ///////////////////////////////////////////////////////////////////////////////
@@ -1477,6 +1485,30 @@
     finishDrawTexture();
 }
 
+void OpenGLRenderer::drawLayer(int texture, float left, float top, float right, float bottom,
+        float u, float v, SkPaint* paint) {
+    if (quickReject(left, top, right, bottom)) {
+        return;
+    }
+
+    glActiveTexture(gTextureUnits[0]);
+    if (!texture) return;
+
+    mCaches.unbindMeshBuffer();
+    resetDrawTextureTexCoords(0.0f, v, u, 0.0f);
+
+    int alpha;
+    SkXfermode::Mode mode;
+    getAlphaAndMode(paint, &alpha, &mode);
+
+    // TODO: Should get the blend info from the caller
+    drawTextureMesh(left, top, right, bottom, texture, alpha / 255.0f, mode, true,
+            &mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
+            GL_TRIANGLE_STRIP, gMeshCount);
+
+    resetDrawTextureTexCoords(0.0f, 0.0f, 1.0f, 1.0f);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Shaders
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 8f93f5b..5f45915 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -64,6 +64,10 @@
     virtual void prepare(bool opaque);
     virtual void finish();
 
+    // These two calls must not be recorded in display lists
+    void interrupt();
+    void resume();
+
     virtual void acquireContext();
     virtual void releaseContext();
 
@@ -91,6 +95,8 @@
     virtual bool clipRect(float left, float top, float right, float bottom, SkRegion::Op op);
 
     virtual void drawDisplayList(DisplayList* displayList);
+    virtual void drawLayer(int texture, float left, float top, float right, float bottom,
+            float u, float v, SkPaint* paint);
     virtual void drawBitmap(SkBitmap* bitmap, float left, float top, SkPaint* paint);
     virtual void drawBitmap(SkBitmap* bitmap, SkMatrix* matrix, SkPaint* paint);
     virtual void drawBitmap(SkBitmap* bitmap, float srcLeft, float srcTop,
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index 0fdc030..e73afa0 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -34,6 +34,15 @@
         </activity>
         
         <activity
+                android:name="ViewLayersActivity2"
+                android:label="_ViewLayers2">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
+        <activity
                 android:name="AlphaLayersActivity"
                 android:label="_αLayers">
             <intent-filter>
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
index aa1eb59..5ddcd20 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsActivity.java
@@ -69,7 +69,7 @@
 
             mMediumPaint = new Paint();
             mMediumPaint.setAntiAlias(true);
-            mMediumPaint.setColor(0xff0000ff);
+            mMediumPaint.setColor(0xe00000ff);
             mMediumPaint.setStrokeWidth(10.0f);
             mMediumPaint.setStyle(Paint.Style.STROKE);
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
new file mode 100644
index 0000000..a0b1d78
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewLayersActivity2.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 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.content.res.Resources;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ViewLayersActivity2 extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        setContentView(R.layout.view_layers);
+
+        setupList(R.id.list1);
+        setupList(R.id.list2);
+        setupList(R.id.list3);
+    }
+
+    private void setupList(int listId) {
+        final ListView list = (ListView) findViewById(listId);
+        list.setAdapter(new SimpleListAdapter(this));
+        list.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+    }
+
+    private static class SimpleListAdapter extends ArrayAdapter<String> {
+        public SimpleListAdapter(Context context) {
+            super(context, android.R.layout.simple_list_item_1, DATA_LIST);
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            TextView v = (TextView) super.getView(position, convertView, parent);
+            final Resources r = getContext().getResources();
+            final DisplayMetrics metrics = r.getDisplayMetrics();
+            v.setCompoundDrawablePadding((int) (6 * metrics.density + 0.5f));
+            v.setCompoundDrawablesWithIntrinsicBounds(r.getDrawable(R.drawable.icon),
+                    null, null, null);
+            return v;
+        }
+    }
+
+    private static final String[] DATA_LIST = {
+            "Afghanistan", "Albania", "Algeria", "American Samoa", "Andorra",
+            "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina",
+            "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan",
+            "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium",
+            "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia",
+            "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil",
+            "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria",
+            "Burkina Faso", "Burundi", "Cote d'Ivoire", "Cambodia", "Cameroon", "Canada", "Cape Verde",
+            "Cayman Islands", "Central African Republic", "Chad", "Chile", "China",
+            "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo",
+            "Cook Islands", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic",
+            "Democratic Republic of the Congo", "Denmark", "Djibouti", "Dominica", "Dominican Republic",
+            "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea",
+            "Estonia", "Ethiopia", "Faeroe Islands", "Falkland Islands", "Fiji", "Finland",
+            "Former Yugoslav Republic of Macedonia", "France", "French Guiana", "French Polynesia",
+            "French Southern Territories", "Gabon", "Georgia", "Germany", "Ghana", "Gibraltar",
+            "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guinea", "Guinea-Bissau",
+            "Guyana", "Haiti", "Heard Island and McDonald Islands", "Honduras", "Hong Kong", "Hungary",
+            "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "Israel", "Italy", "Jamaica",
+            "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Kuwait", "Kyrgyzstan", "Laos",
+            "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg",
+            "Macau", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands",
+            "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia", "Moldova",
+            "Monaco", "Mongolia", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia",
+            "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand",
+            "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "North Korea", "Northern Marianas",
+            "Norway", "Oman", "Pakistan", "Palau", "Panama", "Papua New Guinea", "Paraguay", "Peru",
+            "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar",
+            "Reunion", "Romania", "Russia", "Rwanda", "Sqo Tome and Principe", "Saint Helena",
+            "Saint Kitts and Nevis", "Saint Lucia", "Saint Pierre and Miquelon",
+            "Saint Vincent and the Grenadines", "Samoa", "San Marino", "Saudi Arabia", "Senegal",
+            "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands",
+            "Somalia", "South Africa", "South Georgia and the South Sandwich Islands", "South Korea",
+            "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen", "Swaziland", "Sweden",
+            "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "The Bahamas",
+            "The Gambia", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey",
+            "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Virgin Islands", "Uganda",
+            "Ukraine", "United Arab Emirates", "United Kingdom",
+            "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan",
+            "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Wallis and Futuna", "Western Sahara",
+            "Yemen", "Yugoslavia", "Zambia", "Zimbabwe"
+    };
+}