RenderThread work

 Hacky prototype needs a private API to enable

Change-Id: I21e0ddf3cdbd38a4036354b5d6012449e1a34849
diff --git a/core/java/android/view/GLRenderer.java b/core/java/android/view/GLRenderer.java
index a195231..70428bc 100644
--- a/core/java/android/view/GLRenderer.java
+++ b/core/java/android/view/GLRenderer.java
@@ -635,11 +635,11 @@
     GLRenderer(boolean translucent) {
         mTranslucent = translucent;
 
-        loadSystemProperties(null);
+        loadSystemProperties();
     }
 
     @Override
-    boolean loadSystemProperties(Surface surface) {
+    boolean loadSystemProperties() {
         boolean value;
         boolean changed = false;
 
@@ -1102,11 +1102,6 @@
     }
 
     @Override
-    HardwareCanvas getCanvas() {
-        return mCanvas;
-    }
-
-    @Override
     void setName(String name) {
         mName = name;
     }
@@ -1129,6 +1124,66 @@
     }
 
     @Override
+    void drawDisplayList(DisplayList displayList, View.AttachInfo attachInfo,
+            HardwareDrawCallbacks callbacks, Rect dirty) {
+        if (canDraw()) {
+            if (!hasDirtyRegions()) {
+                dirty = null;
+            }
+
+            // We are already on the correct thread
+            final int surfaceState = checkRenderContextUnsafe();
+            if (surfaceState != SURFACE_STATE_ERROR) {
+                HardwareCanvas canvas = mCanvas;
+
+                if (mProfileEnabled) {
+                    mProfileLock.lock();
+                }
+
+                dirty = beginFrame(canvas, dirty, surfaceState);
+
+                int saveCount = 0;
+                int status = DisplayList.STATUS_DONE;
+
+                long start = getSystemTime();
+                try {
+                    status = prepareFrame(dirty);
+
+                    saveCount = canvas.save();
+                    callbacks.onHardwarePreDraw(canvas);
+
+                    status |= drawDisplayList(attachInfo, canvas, displayList, status);
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "An error has occurred while drawing:", e);
+                } finally {
+                    callbacks.onHardwarePostDraw(canvas);
+                    canvas.restoreToCount(saveCount);
+
+                    mDrawDelta = getSystemTime() - start;
+
+                    if (mDrawDelta > 0) {
+                        mFrameCount++;
+
+                        debugOverdraw(attachInfo, dirty, canvas, displayList);
+                        debugDirtyRegions(dirty, canvas);
+                        drawProfileData(attachInfo);
+                    }
+                }
+
+                onPostDraw();
+
+                swapBuffers(status);
+
+                if (mProfileEnabled) {
+                    mProfileLock.unlock();
+                }
+
+                attachInfo.mIgnoreDirtyState = false;
+            }
+        }
+    }
+
+    @Override
     void draw(View view, View.AttachInfo attachInfo, HardwareDrawCallbacks callbacks,
             Rect dirty) {
         if (canDraw()) {
@@ -1144,7 +1199,6 @@
             final int surfaceState = checkRenderContextUnsafe();
             if (surfaceState != SURFACE_STATE_ERROR) {
                 HardwareCanvas canvas = mCanvas;
-                attachInfo.mHardwareCanvas = canvas;
 
                 if (mProfileEnabled) {
                     mProfileLock.lock();
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 5c0be4a..434d473 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -188,6 +188,9 @@
      */
     public static boolean sSystemRendererDisabled = false;
 
+    /** @hide */
+    public static boolean sUseRenderThread = false;
+
     private boolean mEnabled;
     private boolean mRequested = true;
 
@@ -306,13 +309,6 @@
     abstract int getHeight();
 
     /**
-     * Gets the current canvas associated with this HardwareRenderer.
-     *
-     * @return the current HardwareCanvas
-     */
-    abstract HardwareCanvas getCanvas();
-
-    /**
      * Outputs extra debugging information in the specified file descriptor.
      * @param pw
      */
@@ -335,7 +331,7 @@
      *
      * @return True if a property has changed.
      */
-    abstract boolean loadSystemProperties(Surface surface);
+    abstract boolean loadSystemProperties();
 
     /**
      * Sets the directory to use as a persistent storage for hardware rendering
@@ -412,6 +408,18 @@
             Rect dirty);
 
     /**
+     * Temporary hook to draw a display list directly, only used if sUseRenderThread
+     * is true.
+     *
+     * @param displayList The display list to draw
+     * @param attachInfo AttachInfo tied to the specified view.
+     * @param callbacks Callbacks invoked when drawing happens.
+     * @param dirty The dirty rectangle to update, can be null.
+     */
+    abstract void drawDisplayList(DisplayList displayList, View.AttachInfo attachInfo,
+            HardwareDrawCallbacks callbacks, Rect dirty);
+
+    /**
      * Creates a new hardware layer. A hardware layer built by calling this
      * method will be treated as a texture layer, instead of as a render target.
      *
@@ -517,10 +525,14 @@
      * @return A hardware renderer backed by OpenGL.
      */
     static HardwareRenderer create(boolean translucent) {
+        HardwareRenderer renderer = null;
         if (GLES20Canvas.isAvailable()) {
-            return new GLRenderer(translucent);
+            renderer = new GLRenderer(translucent);
         }
-        return null;
+        if (renderer != null && sUseRenderThread) {
+            renderer = new ThreadedRenderer((GLRenderer)renderer);
+        }
+        return renderer;
     }
 
     /**
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
new file mode 100644
index 0000000..4087313
--- /dev/null
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2013 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.Rect;
+import android.graphics.SurfaceTexture;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.view.Surface.OutOfResourcesException;
+import android.view.View.AttachInfo;
+
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+/**
+ * Hardware renderer that proxies the rendering to a render thread. Most calls
+ * are synchronous, however a few such as draw() are posted async. The display list
+ * is shared between the two threads and is guarded by a top level lock.
+ *
+ * The UI thread can block on the RenderThread, but RenderThread must never
+ * block on the UI thread.
+ *
+ * Note that although currently the EGL context & surfaces are created & managed
+ * by the render thread, the goal is to move that into a shared structure that can
+ * be managed by both threads. EGLSurface creation & deletion should ideally be
+ * done on the UI thread and not the RenderThread to avoid stalling the
+ * RenderThread with surface buffer allocation.
+ *
+ * @hide
+ */
+public class ThreadedRenderer extends HardwareRenderer {
+    private static final String LOGTAG = "ThreadedRenderer";
+
+    @SuppressWarnings("serial")
+    static HashMap<String, Method> sMethodLut = new HashMap<String, Method>() {{
+        Method[] methods = HardwareRenderer.class.getDeclaredMethods();
+        for (Method m : methods) {
+            put(m.getName(), m);
+        }
+    }};
+    static boolean sNeedsInit = true;
+
+    private HardwareRenderer mRemoteRenderer;
+    private int mWidth, mHeight;
+    private RTJob mPreviousDraw;
+
+    ThreadedRenderer(GLRenderer backingRenderer) {
+        mRemoteRenderer = backingRenderer;
+        setEnabled(true);
+        if (sNeedsInit) {
+            sNeedsInit = false;
+            postToRenderThread(new Runnable() {
+                @Override
+                public void run() {
+                    // Hack to allow GLRenderer to create a handler to post the EGL
+                    // destruction to, although it'll never run
+                    Looper.prepare();
+                }
+            });
+        }
+    }
+
+    @Override
+    void destroy(boolean full) {
+        run("destroy", full);
+    }
+
+    @Override
+    boolean initialize(Surface surface) throws OutOfResourcesException {
+        return (Boolean) run("initialize", surface);
+    }
+
+    @Override
+    void updateSurface(Surface surface) throws OutOfResourcesException {
+        post("updateSurface", surface);
+    }
+
+    @Override
+    void destroyLayers(View view) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void destroyHardwareResources(View view) {
+        run("destroyHardwareResources", view);
+    }
+
+    @Override
+    void invalidate(Surface surface) {
+        post("invalidate", surface);
+    }
+
+    @Override
+    boolean validate() {
+        // TODO Remove users of this API
+        return false;
+    }
+
+    @Override
+    boolean safelyRun(Runnable action) {
+        return (Boolean) run("safelyRun", action);
+    }
+
+    @Override
+    void setup(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+        post("setup", width, height);
+    }
+
+    @Override
+    int getWidth() {
+        return mWidth;
+    }
+
+    @Override
+    int getHeight() {
+        return mHeight;
+    }
+
+    @Override
+    void dumpGfxInfo(PrintWriter pw) {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
+    long getFrameCount() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    @Override
+    boolean loadSystemProperties() {
+        return (Boolean) run("loadSystemProperties");
+    }
+
+    @Override
+    void pushLayerUpdate(HardwareLayer layer) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void cancelLayerUpdate(HardwareLayer layer) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void flushLayerUpdates() {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void drawDisplayList(DisplayList displayList, AttachInfo attachInfo,
+            HardwareDrawCallbacks callbacks, Rect dirty) {
+        throw new NoSuchMethodError();
+    }
+
+    /**
+     * TODO: Remove
+     * Temporary hack to allow RenderThreadTest prototype app to trigger
+     * replaying a DisplayList after modifying the displaylist properties
+     *
+     *  @hide */
+    public void repeatLastDraw() {
+        if (mPreviousDraw == null) {
+            throw new IllegalStateException("There isn't a previous draw");
+        }
+        synchronized (mPreviousDraw) {
+            mPreviousDraw.completed = false;
+        }
+        mPreviousDraw.args[3] = null;
+        postToRenderThread(mPreviousDraw);
+    }
+
+    @Override
+    void draw(View view, AttachInfo attachInfo, HardwareDrawCallbacks callbacks, Rect dirty) {
+        requireCompletion(mPreviousDraw);
+
+        attachInfo.mIgnoreDirtyState = true;
+        attachInfo.mDrawingTime = SystemClock.uptimeMillis();
+        view.mPrivateFlags |= View.PFLAG_DRAWN;
+
+        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
+                == View.PFLAG_INVALIDATED;
+        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
+
+        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "getDisplayList");
+        DisplayList displayList = view.getDisplayList();
+        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
+
+        view.mRecreateDisplayList = false;
+
+        mPreviousDraw = post("drawDisplayList", displayList, attachInfo,
+                callbacks, dirty);
+    }
+
+    @Override
+    HardwareLayer createHardwareLayer(boolean isOpaque) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    HardwareLayer createHardwareLayer(int width, int height, boolean isOpaque) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    SurfaceTexture createSurfaceTexture(HardwareLayer layer) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void setSurfaceTexture(HardwareLayer layer, SurfaceTexture surfaceTexture) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void detachFunctor(int functor) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    boolean attachFunctor(AttachInfo attachInfo, int functor) {
+        throw new NoSuchMethodError();
+    }
+
+    @Override
+    void setName(String name) {
+        post("setName", name);
+    }
+
+    private static void requireCompletion(RTJob job) {
+        if (job != null) {
+            synchronized (job) {
+                if (!job.completed) {
+                    try {
+                        job.wait();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        }
+    }
+
+    private RTJob post(String method, Object... args) {
+        RTJob job = new RTJob();
+        job.method = sMethodLut.get(method);
+        job.args = args;
+        job.target = mRemoteRenderer;
+        if (job.method == null) {
+            throw new NullPointerException("Couldn't find method: " + method);
+        }
+        postToRenderThread(job);
+        return job;
+    }
+
+    private Object run(String method, Object... args) {
+        RTJob job = new RTJob();
+        job.method = sMethodLut.get(method);
+        job.args = args;
+        job.target = mRemoteRenderer;
+        if (job.method == null) {
+            throw new NullPointerException("Couldn't find method: " + method);
+        }
+        synchronized (job) {
+            postToRenderThread(job);
+            try {
+                job.wait();
+                return job.ret;
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    static class RTJob implements Runnable {
+        Method method;
+        Object[] args;
+        Object target;
+        Object ret;
+        boolean completed = false;
+
+        @Override
+        public void run() {
+            try {
+                ret = method.invoke(target, args);
+                synchronized (this) {
+                    completed = true;
+                    notify();
+                }
+            } catch (Exception e) {
+                Log.e(LOGTAG, "Failed to invoke: " + method.getName(), e);
+            }
+        }
+    }
+
+    /** @hide */
+    public static native void postToRenderThread(Runnable runnable);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 5d264b6..05366a7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11209,6 +11209,13 @@
     }
 
     /**
+     * @hide
+     */
+    public HardwareRenderer getHardwareRenderer() {
+        return mAttachInfo != null ? mAttachInfo.mHardwareRenderer : null;
+    }
+
+    /**
      * <p>Causes the Runnable to be added to the message queue.
      * The runnable will be run on the user interface thread.</p>
      *
@@ -18843,8 +18850,6 @@
 
         final Callbacks mRootCallbacks;
 
-        HardwareCanvas mHardwareCanvas;
-
         IWindowId mIWindowId;
         WindowId mWindowId;
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ff74f9d..cbaf921 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -266,10 +266,10 @@
     int mScrollY;
     int mCurScrollY;
     Scroller mScroller;
-    HardwareLayer mResizeBuffer;
-    long mResizeBufferStartTime;
-    int mResizeBufferDuration;
-    static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+//    HardwareLayer mResizeBuffer;
+//    long mResizeBufferStartTime;
+//    int mResizeBufferDuration;
+//    static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
     private ArrayList<LayoutTransition> mPendingTransitions;
 
     final ViewConfiguration mViewConfiguration;
@@ -933,17 +933,17 @@
         return mAppVisible ? mView.getVisibility() : View.GONE;
     }
 
-    void disposeResizeBuffer() {
-        if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) {
-            mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
-                @Override
-                public void run() {
-                    mResizeBuffer.destroy();
-                    mResizeBuffer = null;
-                }
-            });
-        }
-    }
+//    void disposeResizeBuffer() {
+//        if (mResizeBuffer != null && mAttachInfo.mHardwareRenderer != null) {
+//            mAttachInfo.mHardwareRenderer.safelyRun(new Runnable() {
+//                @Override
+//                public void run() {
+//                    mResizeBuffer.destroy();
+//                    mResizeBuffer = null;
+//                }
+//            });
+//        }
+//    }
 
     /**
      * Add LayoutTransition to the list of transitions to be started in the next traversal.
@@ -1454,75 +1454,76 @@
                 final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
                         mAttachInfo.mVisibleInsets);
                 if (contentInsetsChanged) {
-                    if (mWidth > 0 && mHeight > 0 && lp != null &&
-                            ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
-                                    & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
-                            mSurface != null && mSurface.isValid() &&
-                            !mAttachInfo.mTurnOffWindowResizeAnim &&
-                            mAttachInfo.mHardwareRenderer != null &&
-                            mAttachInfo.mHardwareRenderer.isEnabled() &&
-                            mAttachInfo.mHardwareRenderer.validate() &&
-                            lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
-
-                        disposeResizeBuffer();
-
-                        boolean completed = false;
-                        HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas();
-                        HardwareCanvas layerCanvas = null;
-                        try {
-                            if (mResizeBuffer == null) {
-                                mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
-                                        mWidth, mHeight, false);
-                            } else if (mResizeBuffer.getWidth() != mWidth ||
-                                    mResizeBuffer.getHeight() != mHeight) {
-                                mResizeBuffer.resize(mWidth, mHeight);
-                            }
-                            // TODO: should handle create/resize failure
-                            layerCanvas = mResizeBuffer.start(hwRendererCanvas);
-                            final int restoreCount = layerCanvas.save();
-
-                            int yoff;
-                            final boolean scrolling = mScroller != null
-                                    && mScroller.computeScrollOffset();
-                            if (scrolling) {
-                                yoff = mScroller.getCurrY();
-                                mScroller.abortAnimation();
-                            } else {
-                                yoff = mScrollY;
-                            }
-
-                            layerCanvas.translate(0, -yoff);
-                            if (mTranslator != null) {
-                                mTranslator.translateCanvas(layerCanvas);
-                            }
-
-                            DisplayList displayList = mView.mDisplayList;
-                            if (displayList != null && displayList.isValid()) {
-                                layerCanvas.drawDisplayList(displayList, null,
-                                        DisplayList.FLAG_CLIP_CHILDREN);
-                            } else {
-                                mView.draw(layerCanvas);
-                            }
-
-                            drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
-
-                            mResizeBufferStartTime = SystemClock.uptimeMillis();
-                            mResizeBufferDuration = mView.getResources().getInteger(
-                                    com.android.internal.R.integer.config_mediumAnimTime);
-                            completed = true;
-
-                            layerCanvas.restoreToCount(restoreCount);
-                        } catch (OutOfMemoryError e) {
-                            Log.w(TAG, "Not enough memory for content change anim buffer", e);
-                        } finally {
-                            if (mResizeBuffer != null) {
-                                mResizeBuffer.end(hwRendererCanvas);
-                                if (!completed) {
-                                    disposeResizeBuffer();
-                                }
-                            }
-                        }
-                    }
+//                    TODO: Do something with this...
+//                    if (mWidth > 0 && mHeight > 0 && lp != null &&
+//                            ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
+//                                    & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
+//                            mSurface != null && mSurface.isValid() &&
+//                            !mAttachInfo.mTurnOffWindowResizeAnim &&
+//                            mAttachInfo.mHardwareRenderer != null &&
+//                            mAttachInfo.mHardwareRenderer.isEnabled() &&
+//                            mAttachInfo.mHardwareRenderer.validate() &&
+//                            lp != null && !PixelFormat.formatHasAlpha(lp.format)) {
+//
+//                        disposeResizeBuffer();
+//
+//                        boolean completed = false;
+//                        HardwareCanvas hwRendererCanvas = mAttachInfo.mHardwareRenderer.getCanvas();
+//                        HardwareCanvas layerCanvas = null;
+//                        try {
+//                            if (mResizeBuffer == null) {
+//                                mResizeBuffer = mAttachInfo.mHardwareRenderer.createHardwareLayer(
+//                                        mWidth, mHeight, false);
+//                            } else if (mResizeBuffer.getWidth() != mWidth ||
+//                                    mResizeBuffer.getHeight() != mHeight) {
+//                                mResizeBuffer.resize(mWidth, mHeight);
+//                            }
+//                            // TODO: should handle create/resize failure
+//                            layerCanvas = mResizeBuffer.start(hwRendererCanvas);
+//                            final int restoreCount = layerCanvas.save();
+//
+//                            int yoff;
+//                            final boolean scrolling = mScroller != null
+//                                    && mScroller.computeScrollOffset();
+//                            if (scrolling) {
+//                                yoff = mScroller.getCurrY();
+//                                mScroller.abortAnimation();
+//                            } else {
+//                                yoff = mScrollY;
+//                            }
+//
+//                            layerCanvas.translate(0, -yoff);
+//                            if (mTranslator != null) {
+//                                mTranslator.translateCanvas(layerCanvas);
+//                            }
+//
+//                            DisplayList displayList = mView.mDisplayList;
+//                            if (displayList != null && displayList.isValid()) {
+//                                layerCanvas.drawDisplayList(displayList, null,
+//                                        DisplayList.FLAG_CLIP_CHILDREN);
+//                            } else {
+//                                mView.draw(layerCanvas);
+//                            }
+//
+//                            drawAccessibilityFocusedDrawableIfNeeded(layerCanvas);
+//
+//                            mResizeBufferStartTime = SystemClock.uptimeMillis();
+//                            mResizeBufferDuration = mView.getResources().getInteger(
+//                                    com.android.internal.R.integer.config_mediumAnimTime);
+//                            completed = true;
+//
+//                            layerCanvas.restoreToCount(restoreCount);
+//                        } catch (OutOfMemoryError e) {
+//                            Log.w(TAG, "Not enough memory for content change anim buffer", e);
+//                        } finally {
+//                            if (mResizeBuffer != null) {
+//                                mResizeBuffer.end(hwRendererCanvas);
+//                                if (!completed) {
+//                                    disposeResizeBuffer();
+//                                }
+//                            }
+//                        }
+//                    }
                     mAttachInfo.mContentInsets.set(mPendingContentInsets);
                     if (DEBUG_LAYOUT) Log.v(TAG, "Content insets changing to: "
                             + mAttachInfo.mContentInsets);
@@ -1582,7 +1583,7 @@
                     if (mScroller != null) {
                         mScroller.abortAnimation();
                     }
-                    disposeResizeBuffer();
+//                    disposeResizeBuffer();
                     // Our surface is gone
                     if (mAttachInfo.mHardwareRenderer != null &&
                             mAttachInfo.mHardwareRenderer.isEnabled()) {
@@ -2181,23 +2182,28 @@
 
     @Override
     public void onHardwarePostDraw(HardwareCanvas canvas) {
-        if (mResizeBuffer != null) {
-            mResizePaint.setAlpha(mResizeAlpha);
-            canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+//        if (mResizeBuffer != null) {
+//            mResizePaint.setAlpha(mResizeAlpha);
+//            canvas.drawHardwareLayer(mResizeBuffer, 0.0f, mHardwareYOffset, mResizePaint);
+//        }
+        // TODO: this
+        if (!HardwareRenderer.sUseRenderThread) {
+            drawAccessibilityFocusedDrawableIfNeeded(canvas);
         }
-        drawAccessibilityFocusedDrawableIfNeeded(canvas);
     }
 
     /**
      * @hide
      */
     void outputDisplayList(View view) {
-        if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
-            DisplayList displayList = view.getDisplayList();
-            if (displayList != null) {
-                mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
-            }
-        }
+        // TODO - route through HardwareCanvas so it can be
+        //        proxied to the correct thread
+//        if (mAttachInfo != null && mAttachInfo.mHardwareCanvas != null) {
+//            DisplayList displayList = view.getDisplayList();
+//            if (displayList != null) {
+//                mAttachInfo.mHardwareCanvas.outputDisplayList(displayList);
+//            }
+//        }
     }
 
     /**
@@ -2342,17 +2348,17 @@
         final boolean scalingRequired = attachInfo.mScalingRequired;
 
         int resizeAlpha = 0;
-        if (mResizeBuffer != null) {
-            long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
-            if (deltaTime < mResizeBufferDuration) {
-                float amt = deltaTime/(float) mResizeBufferDuration;
-                amt = mResizeInterpolator.getInterpolation(amt);
-                animating = true;
-                resizeAlpha = 255 - (int)(amt*255);
-            } else {
-                disposeResizeBuffer();
-            }
-        }
+//        if (mResizeBuffer != null) {
+//            long deltaTime = SystemClock.uptimeMillis() - mResizeBufferStartTime;
+//            if (deltaTime < mResizeBufferDuration) {
+//                float amt = deltaTime/(float) mResizeBufferDuration;
+//                amt = mResizeInterpolator.getInterpolation(amt);
+//                animating = true;
+//                resizeAlpha = 255 - (int)(amt*255);
+//            } else {
+//                disposeResizeBuffer();
+//            }
+//        }
 
         final Rect dirty = mDirty;
         if (mSurfaceHolder != null) {
@@ -2362,7 +2368,7 @@
                 if (mScroller != null) {
                     mScroller.abortAnimation();
                 }
-                disposeResizeBuffer();
+//                disposeResizeBuffer();
             }
             return;
         }
@@ -2725,7 +2731,7 @@
         if (scrollY != mScrollY) {
             if (DEBUG_INPUT_RESIZE) Log.v(TAG, "Pan scroll changed: old="
                     + mScrollY + " , new=" + scrollY);
-            if (!immediate && mResizeBuffer == null) {
+            if (!immediate /*&& mResizeBuffer == null*/) {
                 if (mScroller == null) {
                     mScroller = new Scroller(mView.getContext());
                 }
@@ -5404,7 +5410,7 @@
 
                 // Hardware rendering
                 if (mAttachInfo.mHardwareRenderer != null) {
-                    if (mAttachInfo.mHardwareRenderer.loadSystemProperties(mSurface)) {
+                    if (mAttachInfo.mHardwareRenderer.loadSystemProperties()) {
                         invalidate();
                     }
                 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ca519d1..a558909 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -55,6 +55,7 @@
 	android_view_GLRenderer.cpp \
 	android_view_GLES20DisplayList.cpp \
 	android_view_GLES20Canvas.cpp \
+	android_view_ThreadedRenderer.cpp \
 	android_view_MotionEvent.cpp \
 	android_view_PointerIcon.cpp \
 	android_view_VelocityTracker.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 783e794..b20dc09 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -123,6 +123,7 @@
 extern int register_android_view_GLES20DisplayList(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
 extern int register_android_view_GLRenderer(JNIEnv* env);
+extern int register_android_view_ThreadedRenderer(JNIEnv* env);
 extern int register_android_view_Surface(JNIEnv* env);
 extern int register_android_view_SurfaceControl(JNIEnv* env);
 extern int register_android_view_SurfaceSession(JNIEnv* env);
@@ -1127,6 +1128,7 @@
     REG_JNI(register_android_view_GLES20DisplayList),
     REG_JNI(register_android_view_GLES20Canvas),
     REG_JNI(register_android_view_GLRenderer),
+    REG_JNI(register_android_view_ThreadedRenderer),
     REG_JNI(register_android_view_Surface),
     REG_JNI(register_android_view_SurfaceControl),
     REG_JNI(register_android_view_SurfaceSession),
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
new file mode 100644
index 0000000..8bb6cb4
--- /dev/null
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -0,0 +1,91 @@
+/*
+ * 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 "GLRenderer"
+
+#include "jni.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <renderthread/RenderTask.h>
+#include <renderthread/RenderThread.h>
+
+namespace android {
+
+#ifdef USE_OPENGL_RENDERER
+
+namespace RT = android::uirenderer::renderthread;
+
+static jmethodID gRunnableMethod;
+
+class JavaTask : public RT::RenderTask {
+public:
+    JavaTask(JNIEnv* env, jobject jrunnable) {
+        env->GetJavaVM(&mVm);
+        mRunnable = env->NewGlobalRef(jrunnable);
+    }
+
+    virtual ~JavaTask() {
+        env()->DeleteGlobalRef(mRunnable);
+    }
+
+    virtual void run() {
+        env()->CallVoidMethod(mRunnable, gRunnableMethod);
+    };
+
+private:
+    JNIEnv* env() {
+        JNIEnv* env;
+        if (mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+            return 0;
+        }
+        return env;
+    }
+
+    JavaVM* mVm;
+    jobject mRunnable;
+};
+
+static void android_view_ThreadedRenderer_postToRenderThread(JNIEnv* env, jobject clazz,
+        jobject jrunnable) {
+    RT::RenderTask* task = new JavaTask(env, jrunnable);
+    RT::RenderThread::getInstance().queue(task);
+}
+
+#endif
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/ThreadedRenderer";
+
+static JNINativeMethod gMethods[] = {
+#ifdef USE_OPENGL_RENDERER
+    { "postToRenderThread", "(Ljava/lang/Runnable;)V",   (void*) android_view_ThreadedRenderer_postToRenderThread },
+#endif
+};
+
+int register_android_view_ThreadedRenderer(JNIEnv* env) {
+#ifdef USE_OPENGL_RENDERER
+    jclass cls = env->FindClass("java/lang/Runnable");
+    gRunnableMethod = env->GetMethodID(cls, "run", "()V");
+    env->DeleteLocalRef(cls);
+#endif
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index ce8364e..1f37925 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -45,6 +45,11 @@
 		TextureCache.cpp \
 		TextDropShadowCache.cpp
 
+	# RenderThread stuff
+	LOCAL_SRC_FILES += \
+		renderthread/RenderTask.cpp \
+		renderthread/RenderThread.cpp
+
 	intermediates := $(call intermediates-dir-for,STATIC_LIBRARIES,libRS,TARGET,)
 
 	LOCAL_C_INCLUDES += \
diff --git a/libs/hwui/renderthread/RenderTask.cpp b/libs/hwui/renderthread/RenderTask.cpp
new file mode 100644
index 0000000..2da91c5
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.cpp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 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 "RenderTask"
+
+#include "RenderTask.h"
+
+#include <utils/Log.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+RenderTask::RenderTask() : mNext(0) {
+}
+
+RenderTask::~RenderTask() {
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderTask.h b/libs/hwui/renderthread/RenderTask.h
new file mode 100644
index 0000000..865b1e6
--- /dev/null
+++ b/libs/hwui/renderthread/RenderTask.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2013 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 RENDERTASK_H_
+#define RENDERTASK_H_
+
+#include <cutils/compiler.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class ANDROID_API RenderTask {
+public:
+    ANDROID_API RenderTask();
+    ANDROID_API virtual ~RenderTask();
+
+    ANDROID_API virtual void run() = 0;
+
+    RenderTask* mNext;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTASK_H_ */
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
new file mode 100644
index 0000000..18d9300
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2013 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 "RenderThread"
+
+#include "RenderThread.h"
+
+#include <utils/Log.h>
+
+namespace android {
+using namespace uirenderer::renderthread;
+ANDROID_SINGLETON_STATIC_INSTANCE(RenderThread);
+
+namespace uirenderer {
+namespace renderthread {
+
+RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
+        , mQueueHead(0), mQueueTail(0) {
+    mLooper = new Looper(false);
+    run("RenderThread");
+}
+
+RenderThread::~RenderThread() {
+}
+
+bool RenderThread::threadLoop() {
+    for (;;) {
+        int result = mLooper->pollAll(-1);
+        if (result == ALOOPER_POLL_ERROR) {
+            // TODO Something?
+            break;
+        }
+        // Process our queue, if we have anything
+        while (RenderTask* task = nextTask()) {
+            task->run();
+            delete task;
+        }
+    }
+
+    return false;
+}
+
+void RenderThread::queue(RenderTask* task) {
+    AutoMutex _lock(mLock);
+    if (mQueueTail) {
+        mQueueTail->mNext = task;
+    } else {
+        mQueueHead = task;
+    }
+    mQueueTail = task;
+    if (mQueueHead == task) {
+        // Only wake if this is the first task
+        mLooper->wake();
+    }
+}
+
+RenderTask* RenderThread::nextTask() {
+    AutoMutex _lock(mLock);
+    RenderTask* ret = mQueueHead;
+    if (ret) {
+        if (mQueueTail == mQueueHead) {
+            mQueueTail = mQueueHead = 0;
+        } else {
+            mQueueHead = ret->mNext;
+        }
+        ret->mNext = 0;
+    }
+    return ret;
+}
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
new file mode 100644
index 0000000..4edd575
--- /dev/null
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 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 RENDERTHREAD_H_
+#define RENDERTHREAD_H_
+
+#include "RenderTask.h"
+#include <cutils/compiler.h>
+#include <utils/Looper.h>
+#include <utils/Mutex.h>
+#include <utils/Singleton.h>
+#include <utils/Thread.h>
+
+namespace android {
+namespace uirenderer {
+namespace renderthread {
+
+class ANDROID_API RenderThread : public Thread, public Singleton<RenderThread> {
+public:
+    // RenderThread takes complete ownership of tasks that are queued
+    // and will delete them after they are run
+    ANDROID_API void queue(RenderTask* task);
+
+protected:
+    virtual bool threadLoop();
+
+private:
+    friend class Singleton<RenderThread>;
+
+    RenderThread();
+    virtual ~RenderThread();
+
+    RenderTask* nextTask();
+
+    sp<Looper> mLooper;
+    Mutex mLock;
+
+    RenderTask* mQueueHead;
+    RenderTask* mQueueTail;
+};
+
+} /* namespace renderthread */
+} /* namespace uirenderer */
+} /* namespace android */
+#endif /* RENDERTHREAD_H_ */
diff --git a/tests/RenderThreadTest/Android.mk b/tests/RenderThreadTest/Android.mk
new file mode 100644
index 0000000..bdcba2e
--- /dev/null
+++ b/tests/RenderThreadTest/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := RenderThreadTest
+
+LOCAL_STATIC_JAVA_LIBRARIES += android-common
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/RenderThreadTest/AndroidManifest.xml b/tests/RenderThreadTest/AndroidManifest.xml
new file mode 100644
index 0000000..c76cfce
--- /dev/null
+++ b/tests/RenderThreadTest/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.renderthread"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-sdk
+        android:minSdkVersion="18"
+        android:targetSdkVersion="18" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name"
+        android:theme="@style/AppTheme" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".SubActivity"
+            android:theme="@style/AppTheme.Transparent" />
+    </application>
+
+</manifest>
diff --git a/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png b/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
new file mode 100644
index 0000000..755232d
--- /dev/null
+++ b/tests/RenderThreadTest/res/drawable-xhdpi/starry_night_bg.jpg
Binary files differ
diff --git a/tests/RenderThreadTest/res/layout/activity_main.xml b/tests/RenderThreadTest/res/layout/activity_main.xml
new file mode 100644
index 0000000..1fd5459
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/activity_main.xml
@@ -0,0 +1,12 @@
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity" >
+
+    <ListView android:id="@android:id/list"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:divider="@null" />
+
+</FrameLayout>
diff --git a/tests/RenderThreadTest/res/layout/activity_sub.xml b/tests/RenderThreadTest/res/layout/activity_sub.xml
new file mode 100644
index 0000000..713cee4
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/activity_sub.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <View
+        android:id="@+id/bg_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="@drawable/starry_night_bg" />
+
+    <LinearLayout
+        android:id="@+id/my_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical" >
+
+        <View
+            android:id="@+id/from_left"
+            android:layout_width="match_parent"
+            android:layout_height="48dip"
+            android:background="#7000FF00" />
+
+        <View
+            android:id="@+id/from_right"
+            android:layout_width="match_parent"
+            android:layout_height="0dip"
+            android:layout_margin="80dip"
+            android:layout_weight="1"
+            android:background="#90FF0000" />
+
+        <View
+            android:id="@+id/from_left"
+            android:layout_width="match_parent"
+            android:layout_height="48dip"
+            android:background="#7000FF00" />
+    </LinearLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/RenderThreadTest/res/layout/item_layout.xml b/tests/RenderThreadTest/res/layout/item_layout.xml
new file mode 100644
index 0000000..5bdb1ac
--- /dev/null
+++ b/tests/RenderThreadTest/res/layout/item_layout.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:gravity="center_vertical"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:background="#33b5e5"
+/>
\ No newline at end of file
diff --git a/tests/RenderThreadTest/res/values/strings.xml b/tests/RenderThreadTest/res/values/strings.xml
new file mode 100644
index 0000000..f782e98
--- /dev/null
+++ b/tests/RenderThreadTest/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <string name="app_name">Render Thread</string>
+    <string name="action_settings">Settings</string>
+    <string name="hello_world">Hello world!</string>
+
+</resources>
diff --git a/tests/RenderThreadTest/res/values/styles.xml b/tests/RenderThreadTest/res/values/styles.xml
new file mode 100644
index 0000000..f6b5d6a
--- /dev/null
+++ b/tests/RenderThreadTest/res/values/styles.xml
@@ -0,0 +1,11 @@
+<resources>
+    <!-- Application theme. -->
+    <style name="AppTheme" parent="android:Theme.Holo.Light">
+    </style>
+
+    <style name="AppTheme.Transparent">
+        <item name="android:windowIsTranslucent">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+
+</resources>
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
new file mode 100644
index 0000000..a39aba8
--- /dev/null
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -0,0 +1,158 @@
+
+package com.example.renderthread;
+
+import android.animation.TimeInterpolator;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.DisplayList;
+import android.view.HardwareRenderer;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.SimpleAdapter;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+public class MainActivity extends Activity implements OnItemClickListener {
+
+    static final int DURATION = 400;
+
+    static final String KEY_NAME = "name";
+    static final String KEY_CLASS = "clazz";
+
+    static Map<String,?> make(String name) {
+        Map<String,Object> ret = new HashMap<String,Object>();
+        ret.put(KEY_NAME, name);
+        return ret;
+    }
+
+    @SuppressWarnings("serial")
+    static final ArrayList<Map<String,?>> SAMPLES = new ArrayList<Map<String,?>>() {{
+        for (int i = 1; i < 25; i++) {
+            add(make("List Item: " + i));
+        }
+    }};
+
+    Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        HardwareRenderer.sUseRenderThread = true;
+        setContentView(R.layout.activity_main);
+        ListView lv = (ListView) findViewById(android.R.id.list);
+        lv.setDrawSelectorOnTop(true);
+        lv.setAdapter(new SimpleAdapter(this, SAMPLES,
+                R.layout.item_layout, new String[] { KEY_NAME },
+                new int[] { android.R.id.text1 }));
+        lv.setOnItemClickListener(this);
+        getActionBar().setTitle("MainActivity");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        ListView lv = (ListView) findViewById(android.R.id.list);
+        for (int i = 0; i < lv.getChildCount(); i++) {
+            lv.getChildAt(i).animate().translationY(0).setDuration(DURATION);
+        }
+    }
+
+    private static class DisplayListAnimator {
+        private static final TimeInterpolator sDefaultInterpolator =
+                new AccelerateDecelerateInterpolator();
+
+        DisplayList mDisplayList;
+        float mFromValue;
+        float mDelta;
+        long mDuration = DURATION * 2;
+        long mStartTime;
+
+        DisplayListAnimator(View view, float translateXBy) {
+            mDelta = translateXBy;
+            mFromValue = view.getTranslationY();
+            mDisplayList = view.getDisplayList();
+        }
+
+        boolean animate(long currentTime) {
+            if (mStartTime == 0) mStartTime = currentTime;
+
+            float fraction = (float)(currentTime - mStartTime) / mDuration;
+            if (fraction > 1) {
+                return false;
+            }
+            fraction = sDefaultInterpolator.getInterpolation(fraction);
+            float translation = mFromValue + (mDelta * fraction);
+            mDisplayList.setTranslationY(translation);
+            return fraction < 1f;
+        }
+    }
+
+    private static class AnimationExecutor implements Runnable {
+        DisplayListAnimator[] mAnimations;
+        ThreadedRenderer mRenderer;
+
+        AnimationExecutor(ThreadedRenderer renderer, DisplayListAnimator[] animations) {
+            mRenderer = renderer;
+            mAnimations = animations;
+            ThreadedRenderer.postToRenderThread(this);
+        }
+
+        @Override
+        public void run() {
+            boolean hasMore = false;
+            long now = SystemClock.uptimeMillis();
+            for (DisplayListAnimator animator : mAnimations) {
+                hasMore |= animator.animate(now);
+            }
+            mRenderer.repeatLastDraw();
+            if (hasMore) {
+                ThreadedRenderer.postToRenderThread(this);
+            }
+        }
+
+    }
+
+    @Override
+    public void onItemClick(final AdapterView<?> adapterView, View clickedView,
+            int clickedPosition, long clickedId) {
+        int topPosition = adapterView.getFirstVisiblePosition();
+        int dy = adapterView.getHeight();
+        final DisplayListAnimator[] animators = new DisplayListAnimator[adapterView.getChildCount()];
+        for (int i = 0; i < adapterView.getChildCount(); i++) {
+            int pos = topPosition + i;
+            View child = adapterView.getChildAt(i);
+            float delta = (pos - clickedPosition) * 1.1f;
+            if (delta == 0) delta = -1;
+            animators[i] = new DisplayListAnimator(child, dy * delta);
+        }
+        adapterView.invalidate();
+        adapterView.post(new Runnable() {
+
+            @Override
+            public void run() {
+                new AnimationExecutor((ThreadedRenderer) adapterView.getHardwareRenderer(), animators);
+            }
+        });
+        //mHandler.postDelayed(mLaunchActivity, (long) (DURATION * .4));
+        mLaunchActivity.run();
+    }
+
+    private Runnable mLaunchActivity = new Runnable() {
+
+        @Override
+        public void run() {
+            startActivity(new Intent(MainActivity.this, SubActivity.class));
+            overridePendingTransition(0, 0);
+        }
+    };
+
+}
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java
new file mode 100644
index 0000000..892cbae
--- /dev/null
+++ b/tests/RenderThreadTest/src/com/example/renderthread/SubActivity.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2013 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.example.renderthread;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class SubActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        long before = SystemClock.currentThreadTimeMillis();
+        setContentView(R.layout.activity_sub);
+        getActionBar().setTitle("SubActivity");
+        // Simulate being a real app!
+        while (SystemClock.currentThreadTimeMillis() - before < 100) {
+            View v = new View(this, null);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        ViewGroup container = (ViewGroup) findViewById(R.id.my_container);
+        int dx = getWindowManager().getDefaultDisplay().getWidth();
+        for (int i = 0; i < container.getChildCount(); i++) {
+            View child = container.getChildAt(i);
+            int dir = child.getId() == R.id.from_left ? 1 : -1;
+            child.setTranslationX(dx * dir);
+            child.animate().translationX(0).setDuration(MainActivity.DURATION);
+        }
+        View bg = findViewById(R.id.bg_container);
+        bg.setAlpha(0f);
+        bg.animate().alpha(1f).setDuration(MainActivity.DURATION);
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        overridePendingTransition(0, 0);
+    }
+}