RenderThread animator support

Change-Id: Icf29098edfdaf7ed550bbe9d49e9eaefb4167084
diff --git a/core/java/android/view/RenderNode.java b/core/java/android/view/RenderNode.java
index 3dc09c4..8b80c3e0 100644
--- a/core/java/android/view/RenderNode.java
+++ b/core/java/android/view/RenderNode.java
@@ -20,6 +20,9 @@
 import android.graphics.Matrix;
 import android.graphics.Outline;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * <p>A display list records a series of graphics related operations and can replay
  * them later. Display lists are usually built by recording operations on a
@@ -176,11 +179,24 @@
     private boolean mValid;
     private final long mNativeRenderNode;
 
+    // We need to keep a strong reference to all running animators to ensure that
+    // they can call removeAnimator when they have finished, as the native-side
+    // object can only hold a WeakReference<> to avoid leaking memory due to
+    // cyclic references.
+    private List<RenderNodeAnimator> mActiveAnimators;
+
     private RenderNode(String name) {
         mNativeRenderNode = nCreate(name);
     }
 
     /**
+     * @see RenderNode#adopt(long)
+     */
+    private RenderNode(long nativePtr) {
+        mNativeRenderNode = nativePtr;
+    }
+
+    /**
      * Creates a new display list that can be used to record batches of
      * drawing operations.
      *
@@ -195,6 +211,17 @@
     }
 
     /**
+     * Adopts an existing native render node.
+     *
+     * Note: This will *NOT* incRef() on the native object, however it will
+     * decRef() when it is destroyed. The caller should have already incRef'd it
+     */
+    public static RenderNode adopt(long nativePtr) {
+        return new RenderNode(nativePtr);
+    }
+
+
+    /**
      * Starts recording a display list for the render node. All
      * operations performed on the returned canvas are recorded and
      * stored in this display list.
@@ -822,6 +849,23 @@
     }
 
     ///////////////////////////////////////////////////////////////////////////
+    // Animations
+    ///////////////////////////////////////////////////////////////////////////
+
+    public void addAnimator(RenderNodeAnimator animator) {
+        if (mActiveAnimators == null) {
+            mActiveAnimators = new ArrayList<RenderNodeAnimator>();
+        }
+        mActiveAnimators.add(animator);
+        nAddAnimator(mNativeRenderNode, animator.getNativeAnimator());
+    }
+
+    public void removeAnimator(RenderNodeAnimator animator) {
+        nRemoveAnimator(mNativeRenderNode, animator.getNativeAnimator());
+        mActiveAnimators.remove(animator);
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
     // Native methods
     ///////////////////////////////////////////////////////////////////////////
 
@@ -896,6 +940,13 @@
     private static native void nOutput(long renderNode);
 
     ///////////////////////////////////////////////////////////////////////////
+    // Animations
+    ///////////////////////////////////////////////////////////////////////////
+
+    private static native void nAddAnimator(long renderNode, long animatorPtr);
+    private static native void nRemoveAnimator(long renderNode, long animatorPtr);
+
+    ///////////////////////////////////////////////////////////////////////////
     // Finalization
     ///////////////////////////////////////////////////////////////////////////
 
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
new file mode 100644
index 0000000..b70ae3d
--- /dev/null
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2014 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.util.SparseIntArray;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * @hide
+ */
+public final class RenderNodeAnimator {
+
+    // Keep in sync with enum RenderProperty in Animator.h
+    private static final int TRANSLATION_X = 0;
+    private static final int TRANSLATION_Y = 1;
+    private static final int TRANSLATION_Z = 2;
+    private static final int SCALE_X = 3;
+    private static final int SCALE_Y = 4;
+    private static final int ROTATION = 5;
+    private static final int ROTATION_X = 6;
+    private static final int ROTATION_Y = 7;
+    private static final int X = 8;
+    private static final int Y = 9;
+    private static final int Z = 10;
+    private static final int ALPHA = 11;
+
+    // ViewPropertyAnimator uses a mask for its values, we need to remap them
+    // to the enum values here. RenderPropertyAnimator can't use the mask values
+    // directly as internally it uses a lookup table so it needs the values to
+    // be sequential starting from 0
+    private static final SparseIntArray sViewPropertyAnimatorMap = new SparseIntArray(15) {{
+        put(ViewPropertyAnimator.TRANSLATION_X, TRANSLATION_X);
+        put(ViewPropertyAnimator.TRANSLATION_Y, TRANSLATION_Y);
+        put(ViewPropertyAnimator.TRANSLATION_Z, TRANSLATION_Z);
+        put(ViewPropertyAnimator.SCALE_X, SCALE_X);
+        put(ViewPropertyAnimator.SCALE_Y, SCALE_Y);
+        put(ViewPropertyAnimator.ROTATION, ROTATION);
+        put(ViewPropertyAnimator.ROTATION_X, ROTATION_X);
+        put(ViewPropertyAnimator.ROTATION_Y, ROTATION_Y);
+        put(ViewPropertyAnimator.X, X);
+        put(ViewPropertyAnimator.Y, Y);
+        put(ViewPropertyAnimator.Z, Z);
+        put(ViewPropertyAnimator.ALPHA, ALPHA);
+    }};
+
+    // Keep in sync DeltaValueType in Animator.h
+    private static final int DELTA_TYPE_ABSOLUTE = 0;
+    private static final int DELTA_TYPE_DELTA = 1;
+
+    private RenderNode mTarget;
+    private long mNativePtr;
+
+    public int mapViewPropertyToRenderProperty(int viewProperty) {
+        return sViewPropertyAnimatorMap.get(viewProperty);
+    }
+
+    public RenderNodeAnimator(int property, int deltaType, float deltaValue) {
+        mNativePtr = nCreateAnimator(new WeakReference<RenderNodeAnimator>(this),
+                property, deltaType, deltaValue);
+    }
+
+    public void start(View target) {
+        mTarget = target.mRenderNode;
+        mTarget.addAnimator(this);
+        // Kick off a frame to start the process
+        target.invalidateViewProperty(true, false);
+    }
+
+    public void cancel() {
+        mTarget.removeAnimator(this);
+    }
+
+    public void setDuration(int duration) {
+        nSetDuration(mNativePtr, duration);
+    }
+
+    long getNativeAnimator() {
+        return mNativePtr;
+    }
+
+    private void onFinished() {
+        mTarget.removeAnimator(this);
+    }
+
+    // Called by native
+    private static void callOnFinished(WeakReference<RenderNodeAnimator> weakThis) {
+        RenderNodeAnimator animator = weakThis.get();
+        if (animator != null) {
+            animator.onFinished();
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            nUnref(mNativePtr);
+            mNativePtr = 0;
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private static native long nCreateAnimator(WeakReference<RenderNodeAnimator> weakThis,
+            int property, int deltaValueType, float deltaValue);
+    private static native void nSetDuration(long nativePtr, int duration);
+    private static native void nUnref(long nativePtr);
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index c7a6d41..eaec8ab 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -50,7 +50,7 @@
 public class ThreadedRenderer extends HardwareRenderer {
     private static final String LOGTAG = "ThreadedRenderer";
 
-    private static final Rect NULL_RECT = new Rect(-1, -1, -1, -1);
+    private static final Rect NULL_RECT = new Rect();
 
     private int mWidth, mHeight;
     private long mNativeProxy;
@@ -58,9 +58,10 @@
     private RenderNode mRootNode;
 
     ThreadedRenderer(boolean translucent) {
-        mNativeProxy = nCreateProxy(translucent);
-        mRootNode = RenderNode.create("RootNode");
+        long rootNodePtr = nCreateRootRenderNode();
+        mRootNode = RenderNode.adopt(rootNodePtr);
         mRootNode.setClipToBounds(false);
+        mNativeProxy = nCreateProxy(translucent, rootNodePtr);
     }
 
     @Override
@@ -202,8 +203,7 @@
         if (dirty == null) {
             dirty = NULL_RECT;
         }
-        nDrawDisplayList(mNativeProxy, mRootNode.getNativeDisplayList(),
-                dirty.left, dirty.top, dirty.right, dirty.bottom);
+        nSyncAndDrawFrame(mNativeProxy, dirty.left, dirty.top, dirty.right, dirty.bottom);
     }
 
     @Override
@@ -293,7 +293,8 @@
     /** @hide */
     public static native void postToRenderThread(Runnable runnable);
 
-    private static native long nCreateProxy(boolean translucent);
+    private static native long nCreateRootRenderNode();
+    private static native long nCreateProxy(boolean translucent, long rootRenderNode);
     private static native void nDeleteProxy(long nativeProxy);
 
     private static native boolean nInitialize(long nativeProxy, Surface window);
@@ -302,7 +303,7 @@
     private static native void nSetup(long nativeProxy, int width, int height);
     private static native void nSetDisplayListData(long nativeProxy, long displayList,
             long newData);
-    private static native void nDrawDisplayList(long nativeProxy, long displayList,
+    private static native void nSyncAndDrawFrame(long nativeProxy,
             int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
     private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
     private static native void nDestroyCanvasAndSurface(long nativeProxy);
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index b1aa7b2..11d2622 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -133,19 +133,19 @@
      * Constants used to associate a property being requested and the mechanism used to set
      * the property (this class calls directly into View to set the properties in question).
      */
-    private static final int NONE           = 0x0000;
-    private static final int TRANSLATION_X  = 0x0001;
-    private static final int TRANSLATION_Y  = 0x0002;
-    private static final int TRANSLATION_Z  = 0x0004;
-    private static final int SCALE_X        = 0x0008;
-    private static final int SCALE_Y        = 0x0010;
-    private static final int ROTATION       = 0x0020;
-    private static final int ROTATION_X     = 0x0040;
-    private static final int ROTATION_Y     = 0x0080;
-    private static final int X              = 0x0100;
-    private static final int Y              = 0x0200;
-    private static final int Z              = 0x0400;
-    private static final int ALPHA          = 0x0800;
+    static final int NONE           = 0x0000;
+    static final int TRANSLATION_X  = 0x0001;
+    static final int TRANSLATION_Y  = 0x0002;
+    static final int TRANSLATION_Z  = 0x0004;
+    static final int SCALE_X        = 0x0008;
+    static final int SCALE_Y        = 0x0010;
+    static final int ROTATION       = 0x0020;
+    static final int ROTATION_X     = 0x0040;
+    static final int ROTATION_Y     = 0x0080;
+    static final int X              = 0x0100;
+    static final int Y              = 0x0200;
+    static final int Z              = 0x0400;
+    static final int ALPHA          = 0x0800;
 
     private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | TRANSLATION_Z |
             SCALE_X | SCALE_Y | ROTATION | ROTATION_X | ROTATION_Y | X | Y | Z;
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 711f28a..ee59c8a 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -61,6 +61,7 @@
 	android_view_MotionEvent.cpp \
 	android_view_PointerIcon.cpp \
 	android_view_RenderNode.cpp \
+	android_view_RenderNodeAnimator.cpp \
 	android_view_VelocityTracker.cpp \
 	android_text_AndroidCharacter.cpp \
 	android_text_AndroidBidi.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aa635c6..f964cd2 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -120,6 +120,7 @@
 extern int register_android_graphics_pdf_PdfDocument(JNIEnv* env);
 extern int register_android_view_DisplayEventReceiver(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_GraphicBuffer(JNIEnv* env);
 extern int register_android_view_GLES20Canvas(JNIEnv* env);
 extern int register_android_view_GLRenderer(JNIEnv* env);
@@ -1193,6 +1194,7 @@
     REG_JNI(register_android_graphics_Graphics),
     REG_JNI(register_android_view_DisplayEventReceiver),
     REG_JNI(register_android_view_RenderNode),
+    REG_JNI(register_android_view_RenderNodeAnimator),
     REG_JNI(register_android_view_GraphicBuffer),
     REG_JNI(register_android_view_GLES20Canvas),
     REG_JNI(register_android_view_GLRenderer),
diff --git a/core/jni/android_view_RenderNode.cpp b/core/jni/android_view_RenderNode.cpp
index 3ad2ae5..4715c26 100644
--- a/core/jni/android_view_RenderNode.cpp
+++ b/core/jni/android_view_RenderNode.cpp
@@ -23,6 +23,7 @@
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
 
+#include <Animator.h>
 #include <DisplayListRenderer.h>
 #include <RenderNode.h>
 
@@ -438,6 +439,25 @@
     return renderNode->stagingProperties().getPivotY();
 }
 
+// ----------------------------------------------------------------------------
+// RenderProperties - Animations
+// ----------------------------------------------------------------------------
+
+static void android_view_RenderNode_addAnimator(JNIEnv* env, jobject clazz,
+        jlong renderNodePtr, jlong animatorPtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
+    renderNode->addAnimator(animator);
+}
+
+static void android_view_RenderNode_removeAnimator(JNIEnv* env, jobject clazz,
+        jlong renderNodePtr, jlong animatorPtr) {
+    RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
+    RenderPropertyAnimator* animator = reinterpret_cast<RenderPropertyAnimator*>(animatorPtr);
+    renderNode->removeAnimator(animator);
+}
+
+
 #endif // USE_OPENGL_RENDERER
 
 // ----------------------------------------------------------------------------
@@ -513,6 +533,9 @@
 
     { "nGetPivotX",                "(J)F",  (void*) android_view_RenderNode_getPivotX },
     { "nGetPivotY",                "(J)F",  (void*) android_view_RenderNode_getPivotY },
+
+    { "nAddAnimator",              "(JJ)V", (void*) android_view_RenderNode_addAnimator },
+    { "nRemoveAnimator",           "(JJ)V", (void*) android_view_RenderNode_removeAnimator },
 #endif
 };
 
diff --git a/core/jni/android_view_RenderNodeAnimator.cpp b/core/jni/android_view_RenderNodeAnimator.cpp
new file mode 100644
index 0000000..35cdf60
--- /dev/null
+++ b/core/jni/android_view_RenderNodeAnimator.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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 "OpenGLRenderer"
+
+#include "android_view_RenderNodeAnimator.h"
+
+#include "jni.h"
+#include "GraphicsJNI.h"
+#include <nativehelper/JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include <Animator.h>
+#include <Interpolator.h>
+#include <RenderProperties.h>
+
+namespace android {
+
+using namespace uirenderer;
+
+static struct {
+    jclass clazz;
+
+    jmethodID callOnFinished;
+} gRenderNodeAnimatorClassInfo;
+
+static JNIEnv* getEnv(JavaVM* vm) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        return 0;
+    }
+    return env;
+}
+
+RenderNodeAnimator::RenderNodeAnimator(JNIEnv* env, jobject weakThis,
+                RenderProperty property, DeltaValueType deltaType, float delta)
+        : RenderPropertyAnimator(property, deltaType, delta) {
+    mWeakThis = env->NewGlobalRef(weakThis);
+    env->GetJavaVM(&mJvm);
+}
+
+RenderNodeAnimator::~RenderNodeAnimator() {
+    JNIEnv* env = getEnv(mJvm);
+    env->DeleteGlobalRef(mWeakThis);
+    mWeakThis = NULL;
+}
+
+void RenderNodeAnimator::callOnFinished() {
+    JNIEnv* env = getEnv(mJvm);
+    env->CallStaticVoidMethod(
+            gRenderNodeAnimatorClassInfo.clazz,
+            gRenderNodeAnimatorClassInfo.callOnFinished,
+            mWeakThis);
+}
+
+static jlong createAnimator(JNIEnv* env, jobject clazz, jobject weakThis,
+        jint property, jint deltaType, jfloat deltaValue) {
+    LOG_ALWAYS_FATAL_IF(property < 0 || property > RenderNodeAnimator::ALPHA,
+            "Invalid property %d", property);
+    LOG_ALWAYS_FATAL_IF(deltaType != RenderPropertyAnimator::DELTA
+            && deltaType != RenderPropertyAnimator::ABSOLUTE,
+            "Invalid delta type %d", deltaType);
+
+    RenderNodeAnimator* animator = new RenderNodeAnimator(env, weakThis,
+            static_cast<RenderPropertyAnimator::RenderProperty>(property),
+            static_cast<RenderPropertyAnimator::DeltaValueType>(deltaType),
+            deltaValue);
+    animator->incStrong(0);
+    return reinterpret_cast<jlong>( animator );
+}
+
+static void setDuration(JNIEnv* env, jobject clazz, jlong animatorPtr, jint duration) {
+    LOG_ALWAYS_FATAL_IF(duration < 0, "Duration cannot be negative");
+    RenderNodeAnimator* animator = reinterpret_cast<RenderNodeAnimator*>(animatorPtr);
+    animator->setDuration(duration);
+}
+
+static void unref(JNIEnv* env, jobject clazz, jlong objPtr) {
+    VirtualLightRefBase* obj = reinterpret_cast<VirtualLightRefBase*>(objPtr);
+    obj->decStrong(0);
+}
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+const char* const kClassPathName = "android/view/RenderNodeAnimator";
+
+static JNINativeMethod gMethods[] = {
+    { "nCreateAnimator", "(Ljava/lang/ref/WeakReference;IIF)J", (void*) createAnimator },
+    { "nSetDuration", "(JI)V", (void*) setDuration },
+    { "nUnref", "(J)V", (void*) unref },
+};
+
+#define FIND_CLASS(var, className) \
+        var = env->FindClass(className); \
+        LOG_FATAL_IF(! var, "Unable to find class " className);
+
+#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
+        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
+        LOG_FATAL_IF(! var, "Unable to find method " methodName);
+
+int register_android_view_RenderNodeAnimator(JNIEnv* env) {
+    FIND_CLASS(gRenderNodeAnimatorClassInfo.clazz, kClassPathName);
+    gRenderNodeAnimatorClassInfo.clazz = jclass(env->NewGlobalRef(gRenderNodeAnimatorClassInfo.clazz));
+
+    GET_STATIC_METHOD_ID(gRenderNodeAnimatorClassInfo.callOnFinished, gRenderNodeAnimatorClassInfo.clazz,
+            "callOnFinished", "(Ljava/lang/ref/WeakReference;)V");
+
+    return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
+}
+
+
+} // namespace android
diff --git a/core/jni/android_view_RenderNodeAnimator.h b/core/jni/android_view_RenderNodeAnimator.h
new file mode 100644
index 0000000..d84003f
--- /dev/null
+++ b/core/jni/android_view_RenderNodeAnimator.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include "jni.h"
+
+#include <Animator.h>
+
+namespace android {
+
+class RenderNodeAnimator : public uirenderer::RenderPropertyAnimator {
+public:
+    RenderNodeAnimator(JNIEnv* env, jobject callbackObject,
+            RenderProperty property, DeltaValueType deltaType, float delta);
+    virtual ~RenderNodeAnimator();
+
+    void callOnFinished();
+
+private:
+    JavaVM* mJvm;
+    jobject mWeakThis;
+};
+
+}
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index b5f489d..58fc1e1 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -16,6 +16,8 @@
 
 #define LOG_TAG "ThreadedRenderer"
 
+#include <algorithm>
+
 #include "jni.h"
 #include <nativehelper/JNIHelp.h>
 #include <android_runtime/AndroidRuntime.h>
@@ -24,6 +26,8 @@
 #include <android_runtime/android_view_Surface.h>
 #include <system/window.h>
 
+#include "android_view_RenderNodeAnimator.h"
+#include <RenderNode.h>
 #include <renderthread/RenderProxy.h>
 #include <renderthread/RenderTask.h>
 #include <renderthread/RenderThread.h>
@@ -63,15 +67,75 @@
     jobject mRunnable;
 };
 
+class InvokeAnimationListeners : public MessageHandler {
+public:
+    InvokeAnimationListeners(std::vector< sp<RenderNodeAnimator> >& animators) {
+        mAnimators.swap(animators);
+    }
+
+    static void callOnFinished(const sp<RenderNodeAnimator>& animator) {
+        animator->callOnFinished();
+    }
+
+    virtual void handleMessage(const Message& message) {
+        std::for_each(mAnimators.begin(), mAnimators.end(), callOnFinished);
+        mAnimators.clear();
+    }
+
+private:
+    std::vector< sp<RenderNodeAnimator> > mAnimators;
+};
+
+class RootRenderNode : public RenderNode, public AnimationListener {
+public:
+    RootRenderNode() : RenderNode() {
+        mLooper = Looper::getForThread();
+        LOG_ALWAYS_FATAL_IF(!mLooper.get(),
+                "Must create RootRenderNode on a thread with a looper!");
+    }
+
+    virtual ~RootRenderNode() {}
+
+    void onAnimationFinished(const sp<RenderPropertyAnimator>& animator) {
+        mFinishedAnimators.push_back(
+                reinterpret_cast<RenderNodeAnimator*>(animator.get()));
+    }
+
+    virtual void prepareTree(TreeInfo& info) {
+        info.animationListener = this;
+        RenderNode::prepareTree(info);
+        info.animationListener = NULL;
+
+        // post all the finished stuff
+        if (mFinishedAnimators.size()) {
+            sp<InvokeAnimationListeners> message
+                    = new InvokeAnimationListeners(mFinishedAnimators);
+            mLooper->sendMessage(message, 0);
+        }
+    }
+
+private:
+    sp<Looper> mLooper;
+    std::vector< sp<RenderNodeAnimator> > mFinishedAnimators;
+};
+
 static void android_view_ThreadedRenderer_postToRenderThread(JNIEnv* env, jobject clazz,
         jobject jrunnable) {
     RenderTask* task = new JavaTask(env, jrunnable);
     RenderThread::getInstance().queue(task);
 }
 
+static jlong android_view_ThreadedRenderer_createRootRenderNode(JNIEnv* env, jobject clazz) {
+    RootRenderNode* node = new RootRenderNode();
+    node->incStrong(0);
+    node->setName("RootRenderNode");
+    return reinterpret_cast<jlong>(node);
+}
+
 static jlong android_view_ThreadedRenderer_createProxy(JNIEnv* env, jobject clazz,
-        jboolean translucent) {
-    return (jlong) new RenderProxy(translucent);
+        jboolean translucent, jlong rootRenderNodePtr) {
+    RenderNode* rootRenderNode = reinterpret_cast<RenderNode*>(rootRenderNodePtr);
+    return (jlong) new RenderProxy(translucent, rootRenderNode);
 }
 
 static void android_view_ThreadedRenderer_deleteProxy(JNIEnv* env, jobject clazz,
@@ -113,12 +177,11 @@
     proxy->setup(width, height);
 }
 
-static void android_view_ThreadedRenderer_drawDisplayList(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jlong displayListPtr, jint dirtyLeft, jint dirtyTop,
+static void android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
+        jlong proxyPtr, jint dirtyLeft, jint dirtyTop,
         jint dirtyRight, jint dirtyBottom) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    RenderNode* displayList = reinterpret_cast<RenderNode*>(displayListPtr);
-    proxy->drawDisplayList(displayList, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
+    proxy->syncAndDrawFrame(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
 }
 
 static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz,
@@ -187,13 +250,14 @@
 static JNINativeMethod gMethods[] = {
 #ifdef USE_OPENGL_RENDERER
     { "postToRenderThread", "(Ljava/lang/Runnable;)V",   (void*) android_view_ThreadedRenderer_postToRenderThread },
-    { "nCreateProxy", "(Z)J", (void*) android_view_ThreadedRenderer_createProxy },
+    { "nCreateRootRenderNode", "()J", (void*) android_view_ThreadedRenderer_createRootRenderNode },
+    { "nCreateProxy", "(ZJ)J", (void*) android_view_ThreadedRenderer_createProxy },
     { "nDeleteProxy", "(J)V", (void*) android_view_ThreadedRenderer_deleteProxy },
     { "nInitialize", "(JLandroid/view/Surface;)Z", (void*) android_view_ThreadedRenderer_initialize },
     { "nUpdateSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_updateSurface },
     { "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
     { "nSetup", "(JII)V", (void*) android_view_ThreadedRenderer_setup },
-    { "nDrawDisplayList", "(JJIIII)V", (void*) android_view_ThreadedRenderer_drawDisplayList },
+    { "nSyncAndDrawFrame", "(JIIII)V", (void*) android_view_ThreadedRenderer_syncAndDrawFrame },
     { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
     { "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
     { "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 52be531..d324439 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -11,6 +11,7 @@
 		font/CacheTexture.cpp \
 		font/Font.cpp \
 		AmbientShadow.cpp \
+		Animator.cpp \
 		AssetAtlas.cpp \
 		FontRenderer.cpp \
 		GammaFontRenderer.cpp \
@@ -25,6 +26,7 @@
 		FboCache.cpp \
 		GradientCache.cpp \
 		Image.cpp \
+		Interpolator.cpp \
 		Layer.cpp \
 		LayerCache.cpp \
 		LayerRenderer.cpp \
@@ -66,6 +68,8 @@
 		$(LOCAL_PATH)/../../include/utils \
 		external/skia/src/core
 
+	include external/stlport/libstlport.mk
+
 	LOCAL_CFLAGS += -DUSE_OPENGL_RENDERER -DEGL_EGLEXT_PROTOTYPES -DGL_GLEXT_PROTOTYPES
 	LOCAL_CFLAGS += -Wno-unused-parameter
 	LOCAL_MODULE_CLASS := SHARED_LIBRARIES
diff --git a/libs/hwui/Animator.cpp b/libs/hwui/Animator.cpp
new file mode 100644
index 0000000..ee16586
--- /dev/null
+++ b/libs/hwui/Animator.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2014 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 "RT-Animator"
+
+#include "Animator.h"
+
+#include <set>
+
+#include "RenderProperties.h"
+
+namespace android {
+namespace uirenderer {
+
+/************************************************************
+ *  Private header
+ ************************************************************/
+
+typedef void (RenderProperties::*SetFloatProperty)(float value);
+typedef float (RenderProperties::*GetFloatProperty)() const;
+
+struct PropertyAccessors {
+    GetFloatProperty getter;
+    SetFloatProperty setter;
+};
+
+// Maps RenderProperty enum to accessors
+static const PropertyAccessors PROPERTY_ACCESSOR_LUT[] = {
+    {&RenderProperties::getTranslationX, &RenderProperties::setTranslationX },
+    {&RenderProperties::getTranslationY, &RenderProperties::setTranslationY },
+    {&RenderProperties::getTranslationZ, &RenderProperties::setTranslationZ },
+    {&RenderProperties::getScaleX, &RenderProperties::setScaleX },
+    {&RenderProperties::getScaleY, &RenderProperties::setScaleY },
+    {&RenderProperties::getRotation, &RenderProperties::setRotation },
+    {&RenderProperties::getRotationX, &RenderProperties::setRotationX },
+    {&RenderProperties::getRotationY, &RenderProperties::setRotationY },
+    {&RenderProperties::getX, &RenderProperties::setX },
+    {&RenderProperties::getY, &RenderProperties::setY },
+    {&RenderProperties::getZ, &RenderProperties::setZ },
+    {&RenderProperties::getAlpha, &RenderProperties::setAlpha },
+};
+
+// Helper class to contain generic animator helpers
+class BaseAnimator {
+public:
+    BaseAnimator();
+    virtual ~BaseAnimator();
+
+    void setInterpolator(Interpolator* interpolator);
+    void setDuration(nsecs_t durationInMs);
+
+    bool isFinished() { return mPlayState == FINISHED; }
+
+protected:
+    // This is the main animation entrypoint that subclasses should call
+    // to generate the onAnimation* lifecycle events
+    // Returns true if the animation has finished, false otherwise
+    bool animateFrame(nsecs_t frameTime);
+
+    // Called when PlayState switches from PENDING to RUNNING
+    virtual void onAnimationStarted() {}
+    virtual void onAnimationUpdated(float fraction) = 0;
+    virtual void onAnimationFinished() {}
+
+private:
+    enum PlayState {
+        PENDING,
+        RUNNING,
+        FINISHED,
+    };
+
+    Interpolator* mInterpolator;
+    PlayState mPlayState;
+    long mStartTime;
+    long mDuration;
+};
+
+// Hide the base classes & private bits from the exported RenderPropertyAnimator
+// in this Impl class so that subclasses of RenderPropertyAnimator don't require
+// knowledge of the inner guts but only the public virtual methods.
+// Animates a single property
+class RenderPropertyAnimatorImpl : public BaseAnimator {
+public:
+    RenderPropertyAnimatorImpl(GetFloatProperty getter, SetFloatProperty setter,
+            RenderPropertyAnimator::DeltaValueType deltaType, float delta);
+    ~RenderPropertyAnimatorImpl();
+
+    bool animate(RenderProperties* target, TreeInfo& info);
+
+protected:
+    virtual void onAnimationStarted();
+    virtual void onAnimationUpdated(float fraction);
+
+private:
+    // mTarget is only valid inside animate()
+    RenderProperties* mTarget;
+    GetFloatProperty mGetter;
+    SetFloatProperty mSetter;
+
+    RenderPropertyAnimator::DeltaValueType mDeltaValueType;
+    float mDeltaValue;
+    float mFromValue;
+};
+
+RenderPropertyAnimator::RenderPropertyAnimator(RenderProperty property,
+        DeltaValueType deltaType, float deltaValue) {
+    PropertyAccessors pa = PROPERTY_ACCESSOR_LUT[property];
+    mImpl = new RenderPropertyAnimatorImpl(pa.getter, pa.setter, deltaType, deltaValue);
+}
+
+RenderPropertyAnimator::~RenderPropertyAnimator() {
+    delete mImpl;
+    mImpl = NULL;
+}
+
+void RenderPropertyAnimator::setInterpolator(Interpolator* interpolator) {
+    mImpl->setInterpolator(interpolator);
+}
+
+void RenderPropertyAnimator::setDuration(nsecs_t durationInMs) {
+    mImpl->setDuration(durationInMs);
+}
+
+bool RenderPropertyAnimator::isFinished() {
+    return mImpl->isFinished();
+}
+
+bool RenderPropertyAnimator::animate(RenderProperties* target, TreeInfo& info) {
+    return mImpl->animate(target, info);
+}
+
+
+/************************************************************
+ *  Base animator
+ ************************************************************/
+
+BaseAnimator::BaseAnimator()
+        : mInterpolator(0)
+        , mPlayState(PENDING)
+        , mStartTime(0)
+        , mDuration(300) {
+
+}
+
+BaseAnimator::~BaseAnimator() {
+    setInterpolator(NULL);
+}
+
+void BaseAnimator::setInterpolator(Interpolator* interpolator) {
+    delete mInterpolator;
+    mInterpolator = interpolator;
+}
+
+void BaseAnimator::setDuration(nsecs_t duration) {
+    mDuration = duration;
+}
+
+bool BaseAnimator::animateFrame(nsecs_t frameTime) {
+    if (mPlayState == PENDING) {
+        mPlayState = RUNNING;
+        mStartTime = frameTime;
+        // No interpolator was set, use the default
+        if (!mInterpolator) {
+            setInterpolator(Interpolator::createDefaultInterpolator());
+        }
+        onAnimationStarted();
+    }
+
+    float fraction = 1.0f;
+    if (mPlayState == RUNNING) {
+        fraction = mDuration > 0 ? (float)(frameTime - mStartTime) / mDuration : 1.0f;
+        if (fraction >= 1.0f) {
+            fraction = 1.0f;
+            mPlayState = FINISHED;
+        }
+    }
+    fraction = mInterpolator->interpolate(fraction);
+    onAnimationUpdated(fraction);
+
+    if (mPlayState == FINISHED) {
+        onAnimationFinished();
+        return true;
+    }
+    return false;
+}
+
+/************************************************************
+ *  RenderPropertyAnimator
+ ************************************************************/
+
+RenderPropertyAnimatorImpl::RenderPropertyAnimatorImpl(
+                GetFloatProperty getter, SetFloatProperty setter,
+                RenderPropertyAnimator::DeltaValueType deltaType, float delta)
+        : mTarget(0)
+        , mGetter(getter)
+        , mSetter(setter)
+        , mDeltaValueType(deltaType)
+        , mDeltaValue(delta)
+        , mFromValue(-1) {
+}
+
+RenderPropertyAnimatorImpl::~RenderPropertyAnimatorImpl() {
+}
+
+bool RenderPropertyAnimatorImpl::animate(RenderProperties* target, TreeInfo& info) {
+    mTarget = target;
+    bool finished = animateFrame(info.frameTimeMs);
+    mTarget = NULL;
+    return finished;
+}
+
+void RenderPropertyAnimatorImpl::onAnimationStarted() {
+    mFromValue = (mTarget->*mGetter)();
+
+    if (mDeltaValueType == RenderPropertyAnimator::ABSOLUTE) {
+        mDeltaValue = (mDeltaValue - mFromValue);
+        mDeltaValueType = RenderPropertyAnimator::DELTA;
+    }
+}
+
+void RenderPropertyAnimatorImpl::onAnimationUpdated(float fraction) {
+    float value = mFromValue + (mDeltaValue * fraction);
+    (mTarget->*mSetter)(value);
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Animator.h b/libs/hwui/Animator.h
new file mode 100644
index 0000000..1c8361b
--- /dev/null
+++ b/libs/hwui/Animator.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 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 ANIMATOR_H
+#define ANIMATOR_H
+
+#include <cutils/compiler.h>
+
+#include "Interpolator.h"
+#include "TreeInfo.h"
+#include "utils/VirtualLightRefBase.h"
+
+namespace android {
+namespace uirenderer {
+
+class RenderProperties;
+class RenderPropertyAnimatorImpl;
+
+class RenderPropertyAnimator : public VirtualLightRefBase {
+public:
+    // Since the UI thread doesn't necessarily know what the current values
+    // actually are and thus can't do the calculations, this is used to inform
+    // the animator how to lazy-resolve the input value
+    enum DeltaValueType {
+        // The delta value represents an absolute value endpoint
+        // mDeltaValue needs to be recalculated to be mDelta = (mDelta - fromValue)
+        // in onAnimationStarted()
+        ABSOLUTE = 0,
+        // The final value represents an offset from the current value
+        // No recalculation is needed
+        DELTA,
+    };
+
+    enum RenderProperty {
+        TRANSLATION_X = 0,
+        TRANSLATION_Y,
+        TRANSLATION_Z,
+        SCALE_X,
+        SCALE_Y,
+        ROTATION,
+        ROTATION_X,
+        ROTATION_Y,
+        X,
+        Y,
+        Z,
+        ALPHA,
+    };
+
+    ANDROID_API void setInterpolator(Interpolator* interpolator);
+    ANDROID_API void setDuration(nsecs_t durationInMs);
+    ANDROID_API bool isFinished();
+
+    bool animate(RenderProperties* target, TreeInfo& info);
+
+protected:
+    ANDROID_API RenderPropertyAnimator(RenderProperty property, DeltaValueType deltaType,
+            float deltaValue);
+    ANDROID_API virtual ~RenderPropertyAnimator();
+
+private:
+    RenderPropertyAnimatorImpl* mImpl;
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* ANIMATOR_H */
diff --git a/libs/hwui/Interpolator.cpp b/libs/hwui/Interpolator.cpp
new file mode 100644
index 0000000..004ddf5
--- /dev/null
+++ b/libs/hwui/Interpolator.cpp
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+#include "Interpolator.h"
+
+#include <math.h>
+
+namespace android {
+namespace uirenderer {
+
+Interpolator* Interpolator::createDefaultInterpolator() {
+    return new AccelerateDecelerateInterpolator();
+}
+
+float AccelerateDecelerateInterpolator::interpolate(float input) {
+    return (float)(cosf((input + 1) * M_PI) / 2.0f) + 0.5f;
+}
+
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/Interpolator.h b/libs/hwui/Interpolator.h
new file mode 100644
index 0000000..2cfb60c
--- /dev/null
+++ b/libs/hwui/Interpolator.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 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 INTERPOLATOR_H
+#define INTERPOLATOR_H
+
+namespace android {
+namespace uirenderer {
+
+class Interpolator {
+public:
+    virtual ~Interpolator() {}
+
+    virtual float interpolate(float input) = 0;
+
+    static Interpolator* createDefaultInterpolator();
+
+protected:
+    Interpolator() {}
+};
+
+class AccelerateDecelerateInterpolator : public Interpolator {
+public:
+    AccelerateDecelerateInterpolator() {}
+    virtual ~AccelerateDecelerateInterpolator() {}
+
+    virtual float interpolate(float input);
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* INTERPOLATOR_H */
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index 838e5ac..dcd6bda 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -18,6 +18,8 @@
 
 #include "RenderNode.h"
 
+#include <algorithm>
+
 #include <SkCanvas.h>
 #include <algorithm>
 
@@ -54,7 +56,8 @@
         : mNeedsPropertiesSync(false)
         , mNeedsDisplayListDataSync(false)
         , mDisplayListData(0)
-        , mStagingDisplayListData(0) {
+        , mStagingDisplayListData(0)
+        , mNeedsAnimatorsSync(false) {
 }
 
 RenderNode::~RenderNode() {
@@ -97,15 +100,32 @@
 }
 
 void RenderNode::prepareTreeImpl(TreeInfo& info) {
-    pushStagingChanges(info);
+    if (info.performStagingPush) {
+        pushStagingChanges(info);
+    }
+    if (info.evaluateAnimations) {
+        evaluateAnimations(info);
+    }
     prepareSubTree(info, mDisplayListData);
 }
 
+static bool is_finished(const sp<RenderPropertyAnimator>& animator) {
+    return animator->isFinished();
+}
+
 void RenderNode::pushStagingChanges(TreeInfo& info) {
     if (mNeedsPropertiesSync) {
         mNeedsPropertiesSync = false;
         mProperties = mStagingProperties;
     }
+    if (mNeedsAnimatorsSync) {
+        mAnimators.reserve(mStagingAnimators.size());
+        std::vector< sp<RenderPropertyAnimator> >::iterator it;
+        // hint: this means copy_if_not()
+        it = std::remove_copy_if(mStagingAnimators.begin(), mStagingAnimators.end(),
+                mAnimators.begin(), is_finished);
+        mAnimators.resize(std::distance(mAnimators.begin(), it));
+    }
     if (mNeedsDisplayListDataSync) {
         mNeedsDisplayListDataSync = false;
         // Do a push pass on the old tree to handle freeing DisplayListData
@@ -119,6 +139,34 @@
     }
 }
 
+class AnimateFunctor {
+public:
+    AnimateFunctor(RenderProperties* target, TreeInfo& info)
+            : mTarget(target), mInfo(info) {}
+
+    bool operator() (sp<RenderPropertyAnimator>& animator) {
+        bool finished = animator->animate(mTarget, mInfo);
+        if (finished && mInfo.animationListener) {
+            mInfo.animationListener->onAnimationFinished(animator);
+        }
+        return finished;
+    }
+private:
+    RenderProperties* mTarget;
+    TreeInfo& mInfo;
+};
+
+void RenderNode::evaluateAnimations(TreeInfo& info) {
+    if (!mAnimators.size()) return;
+
+    AnimateFunctor functor(&mProperties, info);
+    std::vector< sp<RenderPropertyAnimator> >::iterator newEnd;
+    newEnd = std::remove_if(mAnimators.begin(), mAnimators.end(), functor);
+    mAnimators.erase(newEnd, mAnimators.end());
+    mProperties.updateMatrix();
+    info.hasAnimations |= mAnimators.size();
+}
+
 void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
     if (subtree) {
         TextureCache& cache = Caches::getInstance().textureCache;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index b9edbe5..294f436 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -20,6 +20,9 @@
     #define LOG_TAG "OpenGLRenderer"
 #endif
 
+#include <set>
+#include <vector>
+
 #include <SkCamera.h>
 #include <SkMatrix.h>
 
@@ -41,6 +44,7 @@
 #include "DeferredDisplayList.h"
 #include "DisplayList.h"
 #include "RenderProperties.h"
+#include "TreeInfo.h"
 #include "utils/VirtualLightRefBase.h"
 
 class SkBitmap;
@@ -65,17 +69,6 @@
 class RestoreToCountOp;
 class DrawDisplayListOp;
 
-struct TreeInfo {
-    TreeInfo()
-            : hasFunctors(false)
-            , prepareTextures(false)
-    {}
-
-    bool hasFunctors;
-    bool prepareTextures;
-    // TODO: Damage calculations? Flag to skip staging pushes for RT animations?
-};
-
 /**
  * Primary class for storing recorded canvas commands, as well as per-View/ViewGroup display properties.
  *
@@ -91,7 +84,7 @@
 class RenderNode : public VirtualLightRefBase {
 public:
     ANDROID_API RenderNode();
-    ANDROID_API ~RenderNode();
+    ANDROID_API virtual ~RenderNode();
 
     // See flags defined in DisplayList.java
     enum ReplayFlag {
@@ -152,7 +145,19 @@
         return properties().getHeight();
     }
 
-    ANDROID_API void prepareTree(TreeInfo& info);
+    ANDROID_API virtual void prepareTree(TreeInfo& info);
+
+    // UI thread only!
+    ANDROID_API void addAnimator(const sp<RenderPropertyAnimator>& animator) {
+        mStagingAnimators.insert(animator);
+        mNeedsAnimatorsSync = true;
+    }
+
+    // UI thread only!
+    ANDROID_API void removeAnimator(const sp<RenderPropertyAnimator>& animator) {
+        mStagingAnimators.erase(animator);
+        mNeedsAnimatorsSync = true;
+    }
 
 private:
     typedef key_value_pair_t<float, DrawDisplayListOp*> ZDrawDisplayListOpPair;
@@ -214,6 +219,7 @@
 
     void prepareTreeImpl(TreeInfo& info);
     void pushStagingChanges(TreeInfo& info);
+    void evaluateAnimations(TreeInfo& info);
     void prepareSubTree(TreeInfo& info, DisplayListData* subtree);
 
     String8 mName;
@@ -226,6 +232,10 @@
     DisplayListData* mDisplayListData;
     DisplayListData* mStagingDisplayListData;
 
+    bool mNeedsAnimatorsSync;
+    std::set< sp<RenderPropertyAnimator> > mStagingAnimators;
+    std::vector< sp<RenderPropertyAnimator> > mAnimators;
+
     /**
      * Draw time state - these properties are only set and used during rendering
      */
diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp
index 9ec7297..99de1fc 100644
--- a/libs/hwui/RenderProperties.cpp
+++ b/libs/hwui/RenderProperties.cpp
@@ -51,7 +51,8 @@
 
 RenderProperties::ComputedFields::ComputedFields()
         : mTransformMatrix(NULL)
-        , mClipPath(NULL) {
+        , mClipPath(NULL)
+        , mClipPathOp(SkRegion::kIntersect_Op) {
 }
 
 RenderProperties::ComputedFields::~ComputedFields() {
diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h
index 8fc2dd0..6fc8bce 100644
--- a/libs/hwui/RenderProperties.h
+++ b/libs/hwui/RenderProperties.h
@@ -16,7 +16,9 @@
 #ifndef RENDERNODEPROPERTIES_H
 #define RENDERNODEPROPERTIES_H
 
+#include <algorithm>
 #include <stddef.h>
+#include <vector>
 #include <cutils/compiler.h>
 #include <androidfw/ResourceTypes.h>
 
@@ -24,6 +26,7 @@
 #include <SkMatrix.h>
 #include <SkRegion.h>
 
+#include "Animator.h"
 #include "Rect.h"
 #include "RevealClip.h"
 #include "Outline.h"
@@ -149,6 +152,31 @@
         return mPrimitiveFields.mTranslationZ;
     }
 
+    // Animation helper
+    void setX(float value) {
+        setTranslationX(value - getLeft());
+    }
+
+    // Animation helper
+    float getX() const {
+        return getLeft() + getTranslationX();
+    }
+
+    // Animation helper
+    void setY(float value) {
+        setTranslationY(value - getTop());
+    }
+
+    // Animation helper
+    float getY() const {
+        return getTop() + getTranslationY();
+    }
+
+    // Animation helper
+    void setZ(float value) {
+        setTranslationZ(value - getElevation());
+    }
+
     float getZ() const {
         return getElevation() + getTranslationZ();
     }
@@ -457,7 +485,6 @@
         bool mCaching;
     } mPrimitiveFields;
 
-    // mCameraDistance isn't in mPrimitiveFields as it has a complex setter
     SkMatrix* mStaticMatrix;
     SkMatrix* mAnimationMatrix;
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
new file mode 100644
index 0000000..8957607
--- /dev/null
+++ b/libs/hwui/TreeInfo.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 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 TREEINFO_H
+#define TREEINFO_H
+
+#include <cutils/compiler.h>
+#include <utils/Timers.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+
+class RenderPropertyAnimator;
+
+class AnimationListener {
+public:
+    ANDROID_API virtual void onAnimationFinished(const sp<RenderPropertyAnimator>&) = 0;
+protected:
+    ANDROID_API virtual ~AnimationListener() {}
+};
+
+struct TreeInfo {
+    // The defaults here should be safe for everyone but DrawFrameTask to use as-is.
+    TreeInfo()
+            : hasFunctors(false)
+            , prepareTextures(false)
+            , performStagingPush(true)
+            , frameTimeMs(0)
+            , evaluateAnimations(false)
+            , hasAnimations(false)
+            , animationListener(0)
+    {}
+
+    bool hasFunctors;
+    bool prepareTextures;
+    bool performStagingPush;
+
+    // Animations
+    nsecs_t frameTimeMs;
+    bool evaluateAnimations;
+    // This is only updated if evaluateAnimations is true
+    bool hasAnimations;
+    AnimationListener* animationListener;
+
+    // TODO: Damage calculations
+};
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif /* TREEINFO_H */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 5ce7ba6..63f4b95 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -308,18 +308,20 @@
     return value == EGL_BUFFER_PRESERVED;
 }
 
-CanvasContext::CanvasContext(bool translucent)
+CanvasContext::CanvasContext(bool translucent, RenderNode* rootRenderNode)
         : mRenderThread(RenderThread::getInstance())
         , mEglSurface(EGL_NO_SURFACE)
         , mDirtyRegionsEnabled(false)
         , mOpaque(!translucent)
         , mCanvas(0)
-        , mHaveNewSurface(false) {
+        , mHaveNewSurface(false)
+        , mRootRenderNode(rootRenderNode) {
     mGlobalContext = GlobalContext::get();
 }
 
 CanvasContext::~CanvasContext() {
     destroyCanvasAndSurface();
+    mRenderThread.removeFrameCallback(this);
 }
 
 void CanvasContext::destroyCanvasAndSurface() {
@@ -403,7 +405,16 @@
     }
 }
 
-void CanvasContext::drawDisplayList(RenderNode* displayList, Rect* dirty) {
+void CanvasContext::prepareTree(TreeInfo& info) {
+    mRootRenderNode->prepareTree(info);
+
+    if (info.hasAnimations && !info.hasFunctors) {
+        // TODO: Functors
+        mRenderThread.postFrameCallback(this);
+    }
+}
+
+void CanvasContext::draw(Rect* dirty) {
     LOG_ALWAYS_FATAL_IF(!mCanvas || mEglSurface == EGL_NO_SURFACE,
             "drawDisplayList called on a context with no canvas or surface!");
 
@@ -417,7 +428,7 @@
     }
 
     status_t status;
-    if (dirty) {
+    if (dirty && !dirty->isEmpty()) {
         status = mCanvas->prepareDirty(dirty->left, dirty->top,
                 dirty->right, dirty->bottom, mOpaque);
     } else {
@@ -425,7 +436,7 @@
     }
 
     Rect outBounds;
-    status |= mCanvas->drawDisplayList(displayList, outBounds);
+    status |= mCanvas->drawDisplayList(mRootRenderNode.get(), outBounds);
 
     // TODO: Draw debug info
     // TODO: Performance tracking
@@ -437,6 +448,20 @@
     }
 }
 
+// Called by choreographer to do an RT-driven animation
+void CanvasContext::doFrame(nsecs_t frameTimeNanos) {
+    ATRACE_CALL();
+
+    TreeInfo info;
+    info.evaluateAnimations = true;
+    info.frameTimeMs = nanoseconds_to_milliseconds(frameTimeNanos);
+    info.performStagingPush = false;
+    info.prepareTextures = false;
+
+    prepareTree(info);
+    draw(NULL);
+}
+
 void CanvasContext::invokeFunctor(Functor* functor) {
     ATRACE_CALL();
     DrawGlInfo::Mode mode = DrawGlInfo::kModeProcessNoContext;
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index a3fe591..0873ad4 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -25,6 +25,7 @@
 
 #include "../RenderNode.h"
 #include "RenderTask.h"
+#include "RenderThread.h"
 
 #define FUNCTOR_PROCESS_DELAY 4
 
@@ -39,15 +40,13 @@
 namespace renderthread {
 
 class GlobalContext;
-class CanvasContext;
-class RenderThread;
 
 // This per-renderer class manages the bridge between the global EGL context
 // and the render surface.
-class CanvasContext {
+class CanvasContext : public IFrameCallback {
 public:
-    CanvasContext(bool translucent);
-    ~CanvasContext();
+    CanvasContext(bool translucent, RenderNode* rootRenderNode);
+    virtual ~CanvasContext();
 
     bool initialize(EGLNativeWindowType window);
     void updateSurface(EGLNativeWindowType window);
@@ -55,9 +54,13 @@
     void setup(int width, int height);
     void makeCurrent();
     void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info);
-    void drawDisplayList(RenderNode* displayList, Rect* dirty);
+    void prepareTree(TreeInfo& info);
+    void draw(Rect* dirty);
     void destroyCanvasAndSurface();
 
+    // IFrameCallback, Chroreographer-driven frame callback entry point
+    virtual void doFrame(nsecs_t frameTimeNanos);
+
     bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
 
     void invokeFunctor(Functor* functor);
@@ -82,6 +85,8 @@
     bool mOpaque;
     OpenGLRenderer* mCanvas;
     bool mHaveNewSurface;
+
+    const sp<RenderNode> mRootRenderNode;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index f542d43..ff4be71 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -30,7 +30,7 @@
 namespace uirenderer {
 namespace renderthread {
 
-DrawFrameTask::DrawFrameTask() : mContext(0), mRenderNode(0) {
+DrawFrameTask::DrawFrameTask() : mContext(0) {
 }
 
 DrawFrameTask::~DrawFrameTask() {
@@ -55,25 +55,17 @@
     }
 }
 
-void DrawFrameTask::setRenderNode(RenderNode* renderNode) {
-    LOG_ALWAYS_FATAL_IF(!mContext, "Lifecycle violation, there's no context to setRenderNode with!");
-
-    mRenderNode = renderNode;
-}
-
 void DrawFrameTask::setDirty(int left, int top, int right, int bottom) {
     mDirty.set(left, top, right, bottom);
 }
 
 void DrawFrameTask::drawFrame(RenderThread* renderThread) {
-    LOG_ALWAYS_FATAL_IF(!mRenderNode.get(), "Cannot drawFrame with no render node!");
     LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
 
     postAndWait(renderThread);
 
     // Reset the single-frame data
     mDirty.setEmpty();
-    mRenderNode = 0;
 }
 
 void DrawFrameTask::postAndWait(RenderThread* renderThread) {
@@ -88,8 +80,7 @@
     bool canUnblockUiThread = syncFrameState();
 
     // Grab a copy of everything we need
-    Rect dirtyCopy(mDirty);
-    sp<RenderNode> renderNode = mRenderNode;
+    Rect dirty(mDirty);
     CanvasContext* context = mContext;
 
     // From this point on anything in "this" is *UNSAFE TO ACCESS*
@@ -97,15 +88,20 @@
         unblockUiThread();
     }
 
-    drawRenderNode(context, renderNode.get(), &dirtyCopy);
+    context->draw(&dirty);
 
     if (!canUnblockUiThread) {
         unblockUiThread();
     }
 }
 
-static void prepareTreeInfo(TreeInfo& info) {
+static void initTreeInfo(TreeInfo& info) {
     info.prepareTextures = true;
+    info.performStagingPush = true;
+    info.evaluateAnimations = true;
+    // TODO: Get this from Choreographer
+    nsecs_t frameTimeNs = systemTime(CLOCK_MONOTONIC);
+    info.frameTimeMs = nanoseconds_to_milliseconds(frameTimeNs);
 }
 
 bool DrawFrameTask::syncFrameState() {
@@ -113,9 +109,9 @@
     mContext->makeCurrent();
     Caches::getInstance().textureCache.resetMarkInUse();
     TreeInfo info;
-    prepareTreeInfo(info);
+    initTreeInfo(info);
     mContext->processLayerUpdates(&mLayers, info);
-    mRenderNode->prepareTree(info);
+    mContext->prepareTree(info);
     // If prepareTextures is false, we ran out of texture cache space
     return !info.hasFunctors && info.prepareTextures;
 }
@@ -125,16 +121,6 @@
     mSignal.signal();
 }
 
-void DrawFrameTask::drawRenderNode(CanvasContext* context, RenderNode* renderNode, Rect* dirty) {
-    ATRACE_CALL();
-
-    if (dirty->bottom == -1 && dirty->left == -1
-            && dirty->top == -1 && dirty->right == -1) {
-        dirty = 0;
-    }
-    context->drawDisplayList(renderNode, dirty);
-}
-
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/DrawFrameTask.h b/libs/hwui/renderthread/DrawFrameTask.h
index 055d4cf..c280868 100644
--- a/libs/hwui/renderthread/DrawFrameTask.h
+++ b/libs/hwui/renderthread/DrawFrameTask.h
@@ -53,7 +53,6 @@
     void addLayer(DeferredLayerUpdater* layer);
     void removeLayer(DeferredLayerUpdater* layer);
 
-    void setRenderNode(RenderNode* renderNode);
     void setDirty(int left, int top, int right, int bottom);
     void drawFrame(RenderThread* renderThread);
 
@@ -63,7 +62,6 @@
     void postAndWait(RenderThread* renderThread);
     bool syncFrameState();
     void unblockUiThread();
-    static void drawRenderNode(CanvasContext* context, RenderNode* renderNode, Rect* dirty);
 
     Mutex mLock;
     Condition mSignal;
@@ -73,7 +71,6 @@
     /*********************************************
      *  Single frame data
      *********************************************/
-    sp<RenderNode> mRenderNode;
     Rect mDirty;
 
     /*********************************************
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index ce490f1..87886e6 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -51,15 +51,16 @@
     MethodInvokeRenderTask* task = new MethodInvokeRenderTask((RunnableMethod) Bridge_ ## method); \
     ARGS(method) *args = (ARGS(method) *) task->payload()
 
-CREATE_BRIDGE1(createContext, bool translucent) {
-    return new CanvasContext(args->translucent);
+CREATE_BRIDGE2(createContext, bool translucent, RenderNode* rootRenderNode) {
+    return new CanvasContext(args->translucent, args->rootRenderNode);
 }
 
-RenderProxy::RenderProxy(bool translucent)
+RenderProxy::RenderProxy(bool translucent, RenderNode* rootRenderNode)
         : mRenderThread(RenderThread::getInstance())
         , mContext(0) {
     SETUP_TASK(createContext);
     args->translucent = translucent;
+    args->rootRenderNode = rootRenderNode;
     mContext = (CanvasContext*) postAndWait(task);
     mDrawFrameTask.setContext(mContext);
 }
@@ -133,9 +134,8 @@
     post(task);
 }
 
-void RenderProxy::drawDisplayList(RenderNode* displayList,
+void RenderProxy::syncAndDrawFrame(
         int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom) {
-    mDrawFrameTask.setRenderNode(displayList);
     mDrawFrameTask.setDirty(dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
     mDrawFrameTask.drawFrame(&mRenderThread);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index a112493..eab1395 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -56,14 +56,14 @@
  */
 class ANDROID_API RenderProxy {
 public:
-    ANDROID_API RenderProxy(bool translucent);
+    ANDROID_API RenderProxy(bool translucent, RenderNode* rootNode);
     ANDROID_API virtual ~RenderProxy();
 
     ANDROID_API bool initialize(const sp<ANativeWindow>& window);
     ANDROID_API void updateSurface(const sp<ANativeWindow>& window);
     ANDROID_API void pauseSurface(const sp<ANativeWindow>& window);
     ANDROID_API void setup(int width, int height);
-    ANDROID_API void drawDisplayList(RenderNode* displayList,
+    ANDROID_API void syncAndDrawFrame(
             int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
     ANDROID_API void destroyCanvasAndSurface();
 
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 212f475..e95707a 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -18,9 +18,11 @@
 
 #include "RenderThread.h"
 
+#include <gui/DisplayEventReceiver.h>
+#include <utils/Log.h>
+
 #include "CanvasContext.h"
 #include "RenderProxy.h"
-#include <utils/Log.h>
 
 namespace android {
 using namespace uirenderer::renderthread;
@@ -29,6 +31,14 @@
 namespace uirenderer {
 namespace renderthread {
 
+// Number of events to read at a time from the DisplayEventReceiver pipe.
+// The value should be large enough that we can quickly drain the pipe
+// using just a few large reads.
+static const size_t EVENT_BUFFER_SIZE = 100;
+
+// Slight delay to give the UI time to push us a new frame before we replay
+static const int DISPATCH_FRAME_CALLBACKS_DELAY = 0;
+
 TaskQueue::TaskQueue() : mHead(0), mTail(0) {}
 
 RenderTask* TaskQueue::next() {
@@ -103,8 +113,25 @@
     }
 }
 
+class DispatchFrameCallbacks : public RenderTask {
+private:
+    RenderThread* mRenderThread;
+public:
+    DispatchFrameCallbacks(RenderThread* rt) : mRenderThread(rt) {}
+
+    virtual void run() {
+        mRenderThread->dispatchFrameCallbacks();
+    }
+};
+
 RenderThread::RenderThread() : Thread(true), Singleton<RenderThread>()
-        , mNextWakeup(LLONG_MAX) {
+        , mNextWakeup(LLONG_MAX)
+        , mDisplayEventReceiver(0)
+        , mVsyncRequested(false)
+        , mFrameCallbackTaskPending(false)
+        , mFrameCallbackTask(0)
+        , mFrameTime(0) {
+    mFrameCallbackTask = new DispatchFrameCallbacks(this);
     mLooper = new Looper(false);
     run("RenderThread");
 }
@@ -112,10 +139,86 @@
 RenderThread::~RenderThread() {
 }
 
+void RenderThread::initializeDisplayEventReceiver() {
+    LOG_ALWAYS_FATAL_IF(mDisplayEventReceiver, "Initializing a second DisplayEventReceiver?");
+    mDisplayEventReceiver = new DisplayEventReceiver();
+    status_t status = mDisplayEventReceiver->initCheck();
+    LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver "
+            "failed with status: %d", status);
+
+    // Register the FD
+    mLooper->addFd(mDisplayEventReceiver->getFd(), 0,
+            Looper::EVENT_INPUT, RenderThread::displayEventReceiverCallback, this);
+}
+
+int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
+        ALOGE("Display event receiver pipe was closed or an error occurred.  "
+                "events=0x%x", events);
+        return 0; // remove the callback
+    }
+
+    if (!(events & Looper::EVENT_INPUT)) {
+        ALOGW("Received spurious callback for unhandled poll event.  "
+                "events=0x%x", events);
+        return 1; // keep the callback
+    }
+
+    reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
+
+    return 1; // keep the callback
+}
+
+static nsecs_t latestVsyncEvent(DisplayEventReceiver* receiver) {
+    DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
+    nsecs_t latest = 0;
+    ssize_t n;
+    while ((n = receiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
+        for (ssize_t i = 0; i < n; i++) {
+            const DisplayEventReceiver::Event& ev = buf[i];
+            switch (ev.header.type) {
+            case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
+                latest = ev.header.timestamp;
+                break;
+            }
+        }
+    }
+    if (n < 0) {
+        ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
+    }
+    return latest;
+}
+
+void RenderThread::drainDisplayEventQueue() {
+    nsecs_t vsyncEvent = latestVsyncEvent(mDisplayEventReceiver);
+    if (vsyncEvent > 0) {
+        mVsyncRequested = false;
+        mFrameTime = vsyncEvent;
+        if (!mFrameCallbackTaskPending) {
+            mFrameCallbackTaskPending = true;
+            //queueDelayed(mFrameCallbackTask, DISPATCH_FRAME_CALLBACKS_DELAY);
+            queue(mFrameCallbackTask);
+        }
+    }
+}
+
+void RenderThread::dispatchFrameCallbacks() {
+    mFrameCallbackTaskPending = false;
+
+    std::set<IFrameCallback*> callbacks;
+    mFrameCallbacks.swap(callbacks);
+
+    for (std::set<IFrameCallback*>::iterator it = callbacks.begin(); it != callbacks.end(); it++) {
+        (*it)->doFrame(mFrameTime);
+    }
+}
+
 bool RenderThread::threadLoop() {
+    initializeDisplayEventReceiver();
+
     int timeoutMillis = -1;
     for (;;) {
-        int result = mLooper->pollAll(timeoutMillis);
+        int result = mLooper->pollOnce(timeoutMillis);
         LOG_ALWAYS_FATAL_IF(result == Looper::POLL_ERROR,
                 "RenderThread Looper POLL_ERROR!");
 
@@ -159,6 +262,20 @@
     mQueue.remove(task);
 }
 
+void RenderThread::postFrameCallback(IFrameCallback* callback) {
+    mFrameCallbacks.insert(callback);
+    if (!mVsyncRequested) {
+        mVsyncRequested = true;
+        status_t status = mDisplayEventReceiver->requestNextVsync();
+        LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
+                "requestNextVsync failed with status: %d", status);
+    }
+}
+
+void RenderThread::removeFrameCallback(IFrameCallback* callback) {
+    mFrameCallbacks.erase(callback);
+}
+
 RenderTask* RenderThread::nextTask(nsecs_t* nextWakeup) {
     AutoMutex _lock(mLock);
     RenderTask* next = mQueue.peek();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index e444aa0..b93dfd6 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -18,6 +18,10 @@
 #define RENDERTHREAD_H_
 
 #include "RenderTask.h"
+
+#include <memory>
+#include <set>
+
 #include <cutils/compiler.h>
 #include <utils/Looper.h>
 #include <utils/Mutex.h>
@@ -25,9 +29,13 @@
 #include <utils/Thread.h>
 
 namespace android {
+class DisplayEventReceiver;
+
 namespace uirenderer {
 namespace renderthread {
 
+class DispatchFrameCallbacks;
+
 class TaskQueue {
 public:
     TaskQueue();
@@ -42,6 +50,15 @@
     RenderTask* mTail;
 };
 
+// Mimics android.view.Choreographer.FrameCallback
+class IFrameCallback {
+public:
+    virtual void doFrame(nsecs_t frameTimeNanos) = 0;
+
+protected:
+    ~IFrameCallback() {}
+};
+
 class ANDROID_API RenderThread : public Thread, public Singleton<RenderThread> {
 public:
     // RenderThread takes complete ownership of tasks that are queued
@@ -50,15 +67,25 @@
     void queueDelayed(RenderTask* task, int delayMs);
     void remove(RenderTask* task);
 
+    // Mimics android.view.Choreographer
+    void postFrameCallback(IFrameCallback* callback);
+    void removeFrameCallback(IFrameCallback* callback);
+
 protected:
     virtual bool threadLoop();
 
 private:
     friend class Singleton<RenderThread>;
+    friend class DispatchFrameCallbacks;
 
     RenderThread();
     virtual ~RenderThread();
 
+    void initializeDisplayEventReceiver();
+    static int displayEventReceiverCallback(int fd, int events, void* data);
+    void drainDisplayEventQueue();
+    void dispatchFrameCallbacks();
+
     // Returns the next task to be run. If this returns NULL nextWakeup is set
     // to the time to requery for the nextTask to run. mNextWakeup is also
     // set to this time
@@ -69,6 +96,13 @@
 
     nsecs_t mNextWakeup;
     TaskQueue mQueue;
+
+    DisplayEventReceiver* mDisplayEventReceiver;
+    bool mVsyncRequested;
+    std::set<IFrameCallback*> mFrameCallbacks;
+    bool mFrameCallbackTaskPending;
+    DispatchFrameCallbacks* mFrameCallbackTask;
+    nsecs_t mFrameTime;
 };
 
 } /* namespace renderthread */
diff --git a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
index 09531fd..1d209dd 100644
--- a/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
+++ b/tests/RenderThreadTest/src/com/example/renderthread/MainActivity.java
@@ -1,17 +1,13 @@
 
 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.RenderNode;
 import android.view.HardwareRenderer;
-import android.view.ThreadedRenderer;
+import android.view.RenderNodeAnimator;
 import android.view.View;
-import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ListView;
@@ -23,6 +19,8 @@
 
 public class MainActivity extends Activity implements OnItemClickListener {
 
+    static final int TRANSLATION_Y = 1;
+    static final int DELTA_TYPE_DELTA = 1;
     static final int DURATION = 400;
 
     static final String KEY_NAME = "name";
@@ -66,82 +64,21 @@
         }
     }
 
-    private static class DisplayListAnimator {
-        private static final TimeInterpolator sDefaultInterpolator =
-                new AccelerateDecelerateInterpolator();
-
-        RenderNode 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);
+            RenderNodeAnimator animator = new RenderNodeAnimator(
+                    TRANSLATION_Y, DELTA_TYPE_DELTA, dy * delta);
+            animator.setDuration(DURATION);
+            animator.start(child);
         }
-        adapterView.invalidate();
-        adapterView.post(new Runnable() {
-
-            @Override
-            public void run() {
-                new AnimationExecutor((ThreadedRenderer) adapterView.getHardwareRenderer(), animators);
-            }
-        });
         //mHandler.postDelayed(mLaunchActivity, (long) (DURATION * .4));
         mLaunchActivity.run();
     }