Baby steps

 Run ViewPropertyAnimators with no listeners on the RenderThread

Change-Id: I7ff5300db96c7f4b59b09e3fff8a0df173f132dd
diff --git a/core/java/android/view/RenderNodeAnimator.java b/core/java/android/view/RenderNodeAnimator.java
index c1a4fee..e918119 100644
--- a/core/java/android/view/RenderNodeAnimator.java
+++ b/core/java/android/view/RenderNodeAnimator.java
@@ -48,6 +48,8 @@
     public static final int Y = 9;
     public static final int Z = 10;
     public static final int ALPHA = 11;
+    // The last value in the enum, used for array size initialization
+    public static final int LAST_VALUE = ALPHA;
 
     // Keep in sync with enum PaintFields in Animator.h
     public static final int PAINT_STROKE_WIDTH = 0;
@@ -86,7 +88,7 @@
     private boolean mStarted = false;
     private boolean mFinished = false;
 
-    public int mapViewPropertyToRenderProperty(int viewProperty) {
+    public static int mapViewPropertyToRenderProperty(int viewProperty) {
         return sViewPropertyAnimatorMap.get(viewProperty);
     }
 
@@ -125,11 +127,15 @@
         }
     }
 
+    static boolean isNativeInterpolator(TimeInterpolator interpolator) {
+        return interpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class);
+    }
+
     private void applyInterpolator() {
         if (mInterpolator == null) return;
 
         long ni;
-        if (mInterpolator.getClass().isAnnotationPresent(HasNativeInterpolator.class)) {
+        if (isNativeInterpolator(mInterpolator)) {
             ni = ((NativeInterpolatorFactory)mInterpolator).createNativeInterpolator();
         } else {
             long duration = nGetDuration(mNativePtr.get());
diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java
index 11d2622..3104862 100644
--- a/core/java/android/view/ViewPropertyAnimator.java
+++ b/core/java/android/view/ViewPropertyAnimator.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.animation.TimeInterpolator;
+import android.os.Build;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -109,6 +110,11 @@
     private ValueAnimator mTempValueAnimator;
 
     /**
+     * A RenderThread-driven backend that may intercept startAnimation
+     */
+    private ViewPropertyAnimatorRT mRTBackend;
+
+    /**
      * This listener is the mechanism by which the underlying Animator causes changes to the
      * properties currently being animated, as well as the cleanup after an animation is
      * complete.
@@ -227,7 +233,7 @@
      * values are used to calculate the animated value for a given animation fraction
      * during the animation.
      */
-    private static class NameValuesHolder {
+    static class NameValuesHolder {
         int mNameConstant;
         float mFromValue;
         float mDeltaValue;
@@ -247,6 +253,9 @@
     ViewPropertyAnimator(View view) {
         mView = view;
         view.ensureTransformationInfo();
+        if (view.getContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.L) {
+            mRTBackend = new ViewPropertyAnimatorRT(view);
+        }
     }
 
     /**
@@ -371,6 +380,10 @@
         return this;
     }
 
+    Animator.AnimatorListener getListener() {
+        return mListener;
+    }
+
     /**
      * Sets a listener for update events in the underlying ValueAnimator that runs
      * the property animations. Note that the underlying animator is animating between
@@ -390,6 +403,10 @@
         return this;
     }
 
+    ValueAnimator.AnimatorUpdateListener getUpdateListener() {
+        return mUpdateListener;
+    }
+
     /**
      * Starts the currently pending property animations immediately. Calling <code>start()</code>
      * is optional because all animations start automatically at the next opportunity. However,
@@ -825,12 +842,22 @@
         return this;
     }
 
+    boolean hasActions() {
+        return mPendingSetupAction != null
+                || mPendingCleanupAction != null
+                || mPendingOnStartAction != null
+                || mPendingOnEndAction != null;
+    }
+
     /**
      * Starts the underlying Animator for a set of properties. We use a single animator that
      * simply runs from 0 to 1, and then use that fractional value to set each property
      * value accordingly.
      */
     private void startAnimation() {
+        if (mRTBackend != null && mRTBackend.startAnimation(this)) {
+            return;
+        }
         mView.setHasTransientState(true);
         ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
         ArrayList<NameValuesHolder> nameValueList =
diff --git a/core/java/android/view/ViewPropertyAnimatorRT.java b/core/java/android/view/ViewPropertyAnimatorRT.java
new file mode 100644
index 0000000..709efdb
--- /dev/null
+++ b/core/java/android/view/ViewPropertyAnimatorRT.java
@@ -0,0 +1,118 @@
+/*
+ * 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.animation.TimeInterpolator;
+import android.view.ViewPropertyAnimator.NameValuesHolder;
+
+import com.android.internal.view.animation.FallbackLUTInterpolator;
+
+import java.util.ArrayList;
+
+
+/**
+ * This is a RenderThread driven backend for ViewPropertyAnimator.
+ */
+class ViewPropertyAnimatorRT {
+
+    private final View mView;
+
+    private RenderNodeAnimator mAnimators[] = new RenderNodeAnimator[RenderNodeAnimator.LAST_VALUE + 1];
+
+    ViewPropertyAnimatorRT(View view) {
+        mView = view;
+    }
+
+    /**
+     * @return true if ViewPropertyAnimatorRT handled the animation,
+     *         false if ViewPropertyAnimator needs to handle it
+     */
+    public boolean startAnimation(ViewPropertyAnimator parent) {
+        cancelAnimators(parent.mPendingAnimations);
+        if (!canHandleAnimator(parent)) {
+            return false;
+        }
+        doStartAnimation(parent);
+        return true;
+    }
+
+    private void doStartAnimation(ViewPropertyAnimator parent) {
+        int size = parent.mPendingAnimations.size();
+
+        long startDelay = parent.getStartDelay();
+        long duration = parent.getDuration();
+        TimeInterpolator interpolator = parent.getInterpolator();
+        if (!RenderNodeAnimator.isNativeInterpolator(interpolator)) {
+            interpolator = new FallbackLUTInterpolator(interpolator, duration);
+        }
+        for (int i = 0; i < size; i++) {
+            NameValuesHolder holder = parent.mPendingAnimations.get(i);
+            int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+
+            RenderNodeAnimator animator = new RenderNodeAnimator(property, holder.mFromValue + holder.mDeltaValue);
+            animator.setStartDelay(startDelay);
+            animator.setDuration(duration);
+            animator.setInterpolator(interpolator);
+            animator.setTarget(mView);
+            animator.start();
+        }
+
+        parent.mPendingAnimations.clear();
+    }
+
+    private boolean canHandleAnimator(ViewPropertyAnimator parent) {
+        // TODO: Can we eliminate this entirely?
+        // If RenderNode.animatorProperties() can be toggled to point at staging
+        // instead then RNA can be used as the animators for software as well
+        // as the updateListener fallback paths. If this can be toggled
+        // at the top level somehow, combined with requiresUiRedraw, we could
+        // ensure that RT does not self-animate, allowing for safe driving of
+        // the animators from the UI thread using the same mechanisms
+        // ViewPropertyAnimator does, just with everything sitting on a single
+        // animator subsystem instead of multiple.
+
+        if (parent.getUpdateListener() != null) {
+            return false;
+        }
+        if (parent.getListener() != null) {
+            // TODO support
+            return false;
+        }
+        if (!mView.isHardwareAccelerated()) {
+            // TODO handle this maybe?
+            return false;
+        }
+        if (parent.hasActions()) {
+            return false;
+        }
+        // Here goes nothing...
+        return true;
+    }
+
+    private void cancelAnimators(ArrayList<NameValuesHolder> mPendingAnimations) {
+        int size = mPendingAnimations.size();
+        for (int i = 0; i < size; i++) {
+            NameValuesHolder holder = mPendingAnimations.get(i);
+            int property = RenderNodeAnimator.mapViewPropertyToRenderProperty(holder.mNameConstant);
+            if (mAnimators[property] != null) {
+                mAnimators[property].cancel();
+                mAnimators[property] = null;
+            }
+        }
+    }
+
+}
diff --git a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
index 1feb943..06838c9 100644
--- a/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
+++ b/core/java/com/android/internal/view/animation/FallbackLUTInterpolator.java
@@ -24,10 +24,13 @@
  * Interpolator that builds a lookup table to use. This is a fallback for
  * building a native interpolator from a TimeInterpolator that is not marked
  * with {@link HasNativeInterpolator}
+ *
+ * This implements TimeInterpolator to allow for easier interop with Animators
  */
 @HasNativeInterpolator
-public class FallbackLUTInterpolator implements NativeInterpolatorFactory {
+public class FallbackLUTInterpolator implements NativeInterpolatorFactory, TimeInterpolator {
 
+    private TimeInterpolator mSourceInterpolator;
     private final float mLut[];
 
     /**
@@ -35,6 +38,7 @@
      * interpolator creation
      */
     public FallbackLUTInterpolator(TimeInterpolator interpolator, long duration) {
+        mSourceInterpolator = interpolator;
         mLut = createLUT(interpolator, duration);
     }
 
@@ -63,4 +67,9 @@
         float[] lut = createLUT(interpolator, duration);
         return NativeInterpolatorFactoryHelper.createLutInterpolator(lut);
     }
+
+    @Override
+    public float getInterpolation(float input) {
+        return mSourceInterpolator.getInterpolation(input);
+    }
 }
diff --git a/tests/RenderThreadTest/Android.mk b/tests/RenderThreadTest/Android.mk
index bdcba2e..e07e943 100644
--- a/tests/RenderThreadTest/Android.mk
+++ b/tests/RenderThreadTest/Android.mk
@@ -1,7 +1,7 @@
 LOCAL_PATH:= $(call my-dir)
 include $(CLEAR_VARS)
 
-LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE_TAGS := tests
 
 # Only compile source java files in this apk.
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/tests/RenderThreadTest/AndroidManifest.xml b/tests/RenderThreadTest/AndroidManifest.xml
index c76cfce..a7f4f6e 100644
--- a/tests/RenderThreadTest/AndroidManifest.xml
+++ b/tests/RenderThreadTest/AndroidManifest.xml
@@ -4,10 +4,6 @@
     android:versionCode="1"
     android:versionName="1.0" >
 
-    <uses-sdk
-        android:minSdkVersion="18"
-        android:targetSdkVersion="18" />
-
     <application
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"