Bug 1044133 - Add NineOldAndroids animation library to the tree. r=bnicholson
authorMargaret Leibovic <margaret.leibovic@gmail.com>
Fri, 25 Jul 2014 15:20:13 -0700
changeset 196328 5853d370248d3193b18cbc8d7aa25383c9a63a87
parent 196327 b5ae1c999f27a9025e5beae2cdf27e0ee6b3b94d
child 196329 f59f7e13d384e841d07b4862ea761d3956ec5e41
push id46844
push usercbook@mozilla.com
push dateMon, 28 Jul 2014 14:30:47 +0000
treeherdermozilla-inbound@7dd701896de8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbnicholson
bugs1044133
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1044133 - Add NineOldAndroids animation library to the tree. r=bnicholson
mobile/android/base/Makefile.in
mobile/android/base/moz.build
mobile/android/thirdparty/com/nineoldandroids/animation/Animator.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorInflater.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorListenerAdapter.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/ArgbEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/FloatEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/FloatKeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/IntEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/IntKeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/Keyframe.java
mobile/android/thirdparty/com/nineoldandroids/animation/KeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/ObjectAnimator.java
mobile/android/thirdparty/com/nineoldandroids/animation/PreHoneycombCompat.java
mobile/android/thirdparty/com/nineoldandroids/animation/PropertyValuesHolder.java
mobile/android/thirdparty/com/nineoldandroids/animation/TimeAnimator.java
mobile/android/thirdparty/com/nineoldandroids/animation/TypeEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/ValueAnimator.java
mobile/android/thirdparty/com/nineoldandroids/util/FloatProperty.java
mobile/android/thirdparty/com/nineoldandroids/util/IntProperty.java
mobile/android/thirdparty/com/nineoldandroids/util/NoSuchPropertyException.java
mobile/android/thirdparty/com/nineoldandroids/util/Property.java
mobile/android/thirdparty/com/nineoldandroids/util/ReflectiveProperty.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewHelper.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimator.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorHC.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorICS.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java
mobile/android/thirdparty/com/nineoldandroids/view/animation/AnimatorProxy.java
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -77,16 +77,17 @@ endif
 
 JAVA_CLASSPATH := $(subst $(NULL) ,:,$(strip $(JAVA_CLASSPATH)))
 
 ALL_JARS = \
   gecko-R.jar \
   gecko-browser.jar \
   gecko-mozglue.jar \
   gecko-util.jar \
+  nineoldandroids.jar \
   squareup-picasso.jar \
   sync-thirdparty.jar \
   websockets.jar \
   $(NULL)
 
 ifdef MOZ_WEBRTC
 ALL_JARS += webrtc.jar
 endif
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -473,30 +473,64 @@ if CONFIG['MOZ_CRASHREPORTER']:
     ANDROID_RES_DIRS += [ SRCDIR + '/crashreporter/res' ]
 
 gbjar.sources += sync_java_files
 gbjar.generated_sources += sync_generated_java_files
 gbjar.extra_jars = [
     'gecko-R.jar',
     'gecko-mozglue.jar',
     'gecko-util.jar',
+    'nineoldandroids.jar',
     'squareup-picasso.jar',
     'sync-thirdparty.jar',
     'websockets.jar',
 ]
 
 if CONFIG['MOZ_NATIVE_DEVICES']:
     gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_LIB']]
     gbjar.extra_jars += [CONFIG['ANDROID_MEDIAROUTER_LIB']]
     gbjar.extra_jars += [CONFIG['GOOGLE_PLAY_SERVICES_LIB']]
     gbjar.sources += ['ChromeCast.java']
     gbjar.sources += ['MediaPlayerManager.java']
 
 gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough']
 
+noajar = add_java_jar('nineoldandroids')
+noajar.sources += [ thirdparty_source_dir + f for f in [
+    'com/nineoldandroids/animation/Animator.java',
+    'com/nineoldandroids/animation/AnimatorInflater.java',
+    'com/nineoldandroids/animation/AnimatorListenerAdapter.java',
+    'com/nineoldandroids/animation/AnimatorSet.java',
+    'com/nineoldandroids/animation/ArgbEvaluator.java',
+    'com/nineoldandroids/animation/FloatEvaluator.java',
+    'com/nineoldandroids/animation/FloatKeyframeSet.java',
+    'com/nineoldandroids/animation/IntEvaluator.java',
+    'com/nineoldandroids/animation/IntKeyframeSet.java',
+    'com/nineoldandroids/animation/Keyframe.java',
+    'com/nineoldandroids/animation/KeyframeSet.java',
+    'com/nineoldandroids/animation/ObjectAnimator.java',
+    'com/nineoldandroids/animation/PreHoneycombCompat.java',
+    'com/nineoldandroids/animation/PropertyValuesHolder.java',
+    'com/nineoldandroids/animation/TimeAnimator.java',
+    'com/nineoldandroids/animation/TypeEvaluator.java',
+    'com/nineoldandroids/animation/ValueAnimator.java',
+    'com/nineoldandroids/util/FloatProperty.java',
+    'com/nineoldandroids/util/IntProperty.java',
+    'com/nineoldandroids/util/NoSuchPropertyException.java',
+    'com/nineoldandroids/util/Property.java',
+    'com/nineoldandroids/util/ReflectiveProperty.java',
+    'com/nineoldandroids/view/animation/AnimatorProxy.java',
+    'com/nineoldandroids/view/ViewHelper.java',
+    'com/nineoldandroids/view/ViewPropertyAnimator.java',
+    'com/nineoldandroids/view/ViewPropertyAnimatorHC.java',
+    'com/nineoldandroids/view/ViewPropertyAnimatorICS.java',
+    'com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java'
+] ]
+#noajar.javac_flags += ['-Xlint:all']
+
 spjar = add_java_jar('squareup-picasso')
 spjar.sources += [ thirdparty_source_dir + f for f in [
     'com/squareup/picasso/Action.java',
     'com/squareup/picasso/AssetBitmapHunter.java',
     'com/squareup/picasso/BitmapHunter.java',
     'com/squareup/picasso/Cache.java',
     'com/squareup/picasso/Callback.java',
     'com/squareup/picasso/ContactsPhotoBitmapHunter.java',
@@ -578,16 +612,17 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']
     search_activity = add_java_jar('search-activity')
     search_activity.sources += [search_source_dir + '/' + f for f in search_activity_sources]
     search_activity.javac_flags += ['-Xlint:all']
     search_activity.extra_jars = [
         'gecko-R.jar',
         'gecko-browser.jar',
         'gecko-mozglue.jar',
         'gecko-util.jar',
+        'nineoldandroids.jar'
     ]
 
 generated_recursive_make_targets = ['.aapt.deps', '.locales.deps'] # Captures dependencies on Android manifest and all resources.
 
 generated = add_android_eclipse_library_project('FennecResourcesGenerated')
 generated.package_name = 'org.mozilla.fennec.resources.generated'
 generated.res = OBJDIR + '/res'
 generated.recursive_make_targets += generated_recursive_make_targets
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/Animator.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import java.util.ArrayList;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This is the superclass for classes which provide basic support for animations which can be
+ * started, ended, and have <code>AnimatorListeners</code> added to them.
+ */
+public abstract class Animator implements Cloneable {
+
+
+    /**
+     * The set of listeners to be sent events through the life of an animation.
+     */
+    ArrayList<AnimatorListener> mListeners = null;
+
+    /**
+     * Starts this animation. If the animation has a nonzero startDelay, the animation will start
+     * running after that delay elapses. A non-delayed animation will have its initial
+     * value(s) set immediately, followed by calls to
+     * {@link AnimatorListener#onAnimationStart(Animator)} for any listeners of this animator.
+     *
+     * <p>The animation started by calling this method will be run on the thread that called
+     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+     * this is not the case). Also, if the animation will animate
+     * properties of objects in the view hierarchy, then the calling thread should be the UI
+     * thread for that view hierarchy.</p>
+     *
+     */
+    public void start() {
+    }
+
+    /**
+     * Cancels the animation. Unlike {@link #end()}, <code>cancel()</code> causes the animation to
+     * stop in its tracks, sending an
+     * {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} to
+     * its listeners, followed by an
+     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} message.
+     *
+     * <p>This method must be called on the thread that is running the animation.</p>
+     */
+    public void cancel() {
+    }
+
+    /**
+     * Ends the animation. This causes the animation to assign the end value of the property being
+     * animated, then calling the
+     * {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} method on
+     * its listeners.
+     *
+     * <p>This method must be called on the thread that is running the animation.</p>
+     */
+    public void end() {
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+     *
+     * @return the number of milliseconds to delay running the animation
+     */
+    public abstract long getStartDelay();
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+
+     * @param startDelay The amount of the delay, in milliseconds
+     */
+    public abstract void setStartDelay(long startDelay);
+
+
+    /**
+     * Sets the length of the animation.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     */
+    public abstract Animator setDuration(long duration);
+
+    /**
+     * Gets the length of the animation.
+     *
+     * @return The length of the animation, in milliseconds.
+     */
+    public abstract long getDuration();
+
+    /**
+     * The time interpolator used in calculating the elapsed fraction of this animation. The
+     * interpolator determines whether the animation runs with linear or non-linear motion,
+     * such as acceleration and deceleration. The default value is
+     * {@link android.view.animation.AccelerateDecelerateInterpolator}
+     *
+     * @param value the interpolator to be used by this animation
+     */
+    public abstract void setInterpolator(/*Time*/Interpolator value);
+
+    /**
+     * Returns whether this Animator is currently running (having been started and gone past any
+     * initial startDelay period and not yet ended).
+     *
+     * @return Whether the Animator is running.
+     */
+    public abstract boolean isRunning();
+
+    /**
+     * Returns whether this Animator has been started and not yet ended. This state is a superset
+     * of the state of {@link #isRunning()}, because an Animator with a nonzero
+     * {@link #getStartDelay() startDelay} will return true for {@link #isStarted()} during the
+     * delay phase, whereas {@link #isRunning()} will return true only after the delay phase
+     * is complete.
+     *
+     * @return Whether the Animator has been started and not yet ended.
+     */
+    public boolean isStarted() {
+        // Default method returns value for isRunning(). Subclasses should override to return a
+        // real value.
+        return isRunning();
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent events through the life of an
+     * animation, such as start, repeat, and end.
+     *
+     * @param listener the listener to be added to the current set of listeners for this animation.
+     */
+    public void addListener(AnimatorListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<AnimatorListener>();
+        }
+        mListeners.add(listener);
+    }
+
+    /**
+     * Removes a listener from the set listening to this animation.
+     *
+     * @param listener the listener to be removed from the current set of listeners for this
+     *                 animation.
+     */
+    public void removeListener(AnimatorListener listener) {
+        if (mListeners == null) {
+            return;
+        }
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            mListeners = null;
+        }
+    }
+
+    /**
+     * Gets the set of {@link android.animation.Animator.AnimatorListener} objects that are currently
+     * listening for events on this <code>Animator</code> object.
+     *
+     * @return ArrayList<AnimatorListener> The set of listeners.
+     */
+    public ArrayList<AnimatorListener> getListeners() {
+        return mListeners;
+    }
+
+    /**
+     * Removes all listeners from this object. This is equivalent to calling
+     * <code>getListeners()</code> followed by calling <code>clear()</code> on the
+     * returned list of listeners.
+     */
+    public void removeAllListeners() {
+        if (mListeners != null) {
+            mListeners.clear();
+            mListeners = null;
+        }
+    }
+
+    @Override
+    public Animator clone() {
+        try {
+            final Animator anim = (Animator) super.clone();
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> oldListeners = mListeners;
+                anim.mListeners = new ArrayList<AnimatorListener>();
+                int numListeners = oldListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    anim.mListeners.add(oldListeners.get(i));
+                }
+            }
+            return anim;
+        } catch (CloneNotSupportedException e) {
+           throw new AssertionError();
+        }
+    }
+
+    /**
+     * This method tells the object to use appropriate information to extract
+     * starting values for the animation. For example, a AnimatorSet object will pass
+     * this call to its child objects to tell them to set up the values. A
+     * ObjectAnimator object will use the information it has about its target object
+     * and PropertyValuesHolder objects to get the start values for its properties.
+     * An ValueAnimator object will ignore the request since it does not have enough
+     * information (such as a target object) to gather these values.
+     */
+    public void setupStartValues() {
+    }
+
+    /**
+     * This method tells the object to use appropriate information to extract
+     * ending values for the animation. For example, a AnimatorSet object will pass
+     * this call to its child objects to tell them to set up the values. A
+     * ObjectAnimator object will use the information it has about its target object
+     * and PropertyValuesHolder objects to get the start values for its properties.
+     * An ValueAnimator object will ignore the request since it does not have enough
+     * information (such as a target object) to gather these values.
+     */
+    public void setupEndValues() {
+    }
+
+    /**
+     * Sets the target object whose property will be animated by this animation. Not all subclasses
+     * operate on target objects (for example, {@link ValueAnimator}, but this method
+     * is on the superclass for the convenience of dealing generically with those subclasses
+     * that do handle targets.
+     *
+     * @param target The object being animated
+     */
+    public void setTarget(Object target) {
+    }
+
+    /**
+     * <p>An animation listener receives notifications from an animation.
+     * Notifications indicate animation related events, such as the end or the
+     * repetition of the animation.</p>
+     */
+    public static interface AnimatorListener {
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(Animator animation);
+
+        /**
+         * <p>Notifies the end of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which reached its end.
+         */
+        void onAnimationEnd(Animator animation);
+
+        /**
+         * <p>Notifies the cancellation of the animation. This callback is not invoked
+         * for animations with repeat count set to INFINITE.</p>
+         *
+         * @param animation The animation which was canceled.
+         */
+        void onAnimationCancel(Animator animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(Animator animation);
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorInflater.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.animation;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.content.res.Resources.NotFoundException;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.animation.AnimationUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * This class is used to instantiate animator XML files into Animator objects.
+ * <p>
+ * For performance reasons, inflation relies heavily on pre-processing of
+ * XML files that is done at build time. Therefore, it is not currently possible
+ * to use this inflater with an XmlPullParser over a plain XML file at runtime;
+ * it only works with an XmlPullParser returned from a compiled resource (R.
+ * <em>something</em> file.)
+ */
+public class AnimatorInflater {
+    private static final int[] AnimatorSet = new int[] {
+        /* 0 */ android.R.attr.ordering,
+    };
+    private static final int AnimatorSet_ordering = 0;
+
+    private static final int[] PropertyAnimator = new int[] {
+        /* 0 */ android.R.attr.propertyName,
+    };
+    private static final int PropertyAnimator_propertyName = 0;
+
+    private static final int[] Animator = new int[] {
+        /* 0 */ android.R.attr.interpolator,
+        /* 1 */ android.R.attr.duration,
+        /* 2 */ android.R.attr.startOffset,
+        /* 3 */ android.R.attr.repeatCount,
+        /* 4 */ android.R.attr.repeatMode,
+        /* 5 */ android.R.attr.valueFrom,
+        /* 6 */ android.R.attr.valueTo,
+        /* 7 */ android.R.attr.valueType,
+    };
+    private static final int Animator_interpolator = 0;
+    private static final int Animator_duration = 1;
+    private static final int Animator_startOffset = 2;
+    private static final int Animator_repeatCount = 3;
+    private static final int Animator_repeatMode = 4;
+    private static final int Animator_valueFrom = 5;
+    private static final int Animator_valueTo = 6;
+    private static final int Animator_valueType = 7;
+
+    /**
+     * These flags are used when parsing AnimatorSet objects
+     */
+    private static final int TOGETHER = 0;
+    //private static final int SEQUENTIALLY = 1;
+
+    /**
+     * Enum values used in XML attributes to indicate the value for mValueType
+     */
+    private static final int VALUE_TYPE_FLOAT       = 0;
+    //private static final int VALUE_TYPE_INT         = 1;
+    //private static final int VALUE_TYPE_COLOR       = 4;
+    //private static final int VALUE_TYPE_CUSTOM      = 5;
+
+    /**
+     * Loads an {@link Animator} object from a resource
+     *
+     * @param context Application context used to access resources
+     * @param id The resource id of the animation to load
+     * @return The animator object reference by the specified id
+     * @throws android.content.res.Resources.NotFoundException when the animation cannot be loaded
+     */
+    public static Animator loadAnimator(Context context, int id)
+            throws NotFoundException {
+
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createAnimatorFromXml(context, parser);
+        } catch (XmlPullParserException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException("Can't load animation resource ID #0x" +
+                    Integer.toHexString(id));
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+
+        return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
+    }
+
+    private static Animator createAnimatorFromXml(Context c, XmlPullParser parser,
+            AttributeSet attrs, AnimatorSet parent, int sequenceOrdering)
+            throws XmlPullParserException, IOException {
+
+        Animator anim = null;
+        ArrayList<Animator> childAnims = null;
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+               && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String  name = parser.getName();
+
+            if (name.equals("objectAnimator")) {
+                anim = loadObjectAnimator(c, attrs);
+            } else if (name.equals("animator")) {
+                anim = loadAnimator(c, attrs, null);
+            } else if (name.equals("set")) {
+                anim = new AnimatorSet();
+                TypedArray a = c.obtainStyledAttributes(attrs,
+                        /*com.android.internal.R.styleable.*/AnimatorSet);
+
+                TypedValue orderingValue = new TypedValue();
+                a.getValue(/*com.android.internal.R.styleable.*/AnimatorSet_ordering, orderingValue);
+                int ordering = orderingValue.type == TypedValue.TYPE_INT_DEC ? orderingValue.data : TOGETHER;
+
+                createAnimatorFromXml(c, parser, attrs, (AnimatorSet) anim,  ordering);
+                a.recycle();
+            } else {
+                throw new RuntimeException("Unknown animator name: " + parser.getName());
+            }
+
+            if (parent != null) {
+                if (childAnims == null) {
+                    childAnims = new ArrayList<Animator>();
+                }
+                childAnims.add(anim);
+            }
+        }
+        if (parent != null && childAnims != null) {
+            Animator[] animsArray = new Animator[childAnims.size()];
+            int index = 0;
+            for (Animator a : childAnims) {
+                animsArray[index++] = a;
+            }
+            if (sequenceOrdering == TOGETHER) {
+                parent.playTogether(animsArray);
+            } else {
+                parent.playSequentially(animsArray);
+            }
+        }
+
+        return anim;
+
+    }
+
+    private static ObjectAnimator loadObjectAnimator(Context context, AttributeSet attrs)
+            throws NotFoundException {
+
+        ObjectAnimator anim = new ObjectAnimator();
+
+        loadAnimator(context, attrs, anim);
+
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/PropertyAnimator);
+
+        String propertyName = a.getString(/*com.android.internal.R.styleable.*/PropertyAnimator_propertyName);
+
+        anim.setPropertyName(propertyName);
+
+        a.recycle();
+
+        return anim;
+    }
+
+    /**
+     * Creates a new animation whose parameters come from the specified context and
+     * attributes set.
+     *
+     * @param context the application environment
+     * @param attrs the set of attributes holding the animation parameters
+     */
+    private static ValueAnimator loadAnimator(Context context, AttributeSet attrs, ValueAnimator anim)
+            throws NotFoundException {
+
+        TypedArray a =
+                context.obtainStyledAttributes(attrs, /*com.android.internal.R.styleable.*/Animator);
+
+        long duration = a.getInt(/*com.android.internal.R.styleable.*/Animator_duration, 0);
+
+        long startDelay = a.getInt(/*com.android.internal.R.styleable.*/Animator_startOffset, 0);
+
+        int valueType = a.getInt(/*com.android.internal.R.styleable.*/Animator_valueType,
+                VALUE_TYPE_FLOAT);
+
+        if (anim == null) {
+            anim = new ValueAnimator();
+        }
+        //TypeEvaluator evaluator = null;
+
+        int valueFromIndex = /*com.android.internal.R.styleable.*/Animator_valueFrom;
+        int valueToIndex = /*com.android.internal.R.styleable.*/Animator_valueTo;
+
+        boolean getFloats = (valueType == VALUE_TYPE_FLOAT);
+
+        TypedValue tvFrom = a.peekValue(valueFromIndex);
+        boolean hasFrom = (tvFrom != null);
+        int fromType = hasFrom ? tvFrom.type : 0;
+        TypedValue tvTo = a.peekValue(valueToIndex);
+        boolean hasTo = (tvTo != null);
+        int toType = hasTo ? tvTo.type : 0;
+
+        if ((hasFrom && (fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) ||
+            (hasTo && (toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                (toType <= TypedValue.TYPE_LAST_COLOR_INT))) {
+            // special case for colors: ignore valueType and get ints
+            getFloats = false;
+            anim.setEvaluator(new ArgbEvaluator());
+        }
+
+        if (getFloats) {
+            float valueFrom;
+            float valueTo;
+            if (hasFrom) {
+                if (fromType == TypedValue.TYPE_DIMENSION) {
+                    valueFrom = a.getDimension(valueFromIndex, 0f);
+                } else {
+                    valueFrom = a.getFloat(valueFromIndex, 0f);
+                }
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = a.getDimension(valueToIndex, 0f);
+                    } else {
+                        valueTo = a.getFloat(valueToIndex, 0f);
+                    }
+                    anim.setFloatValues(valueFrom, valueTo);
+                } else {
+                    anim.setFloatValues(valueFrom);
+                }
+            } else {
+                if (toType == TypedValue.TYPE_DIMENSION) {
+                    valueTo = a.getDimension(valueToIndex, 0f);
+                } else {
+                    valueTo = a.getFloat(valueToIndex, 0f);
+                }
+                anim.setFloatValues(valueTo);
+            }
+        } else {
+            int valueFrom;
+            int valueTo;
+            if (hasFrom) {
+                if (fromType == TypedValue.TYPE_DIMENSION) {
+                    valueFrom = (int) a.getDimension(valueFromIndex, 0f);
+                } else if ((fromType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                        (fromType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                    valueFrom = a.getColor(valueFromIndex, 0);
+                } else {
+                    valueFrom = a.getInt(valueFromIndex, 0);
+                }
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = (int) a.getDimension(valueToIndex, 0f);
+                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                            (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        valueTo = a.getColor(valueToIndex, 0);
+                    } else {
+                        valueTo = a.getInt(valueToIndex, 0);
+                    }
+                    anim.setIntValues(valueFrom, valueTo);
+                } else {
+                    anim.setIntValues(valueFrom);
+                }
+            } else {
+                if (hasTo) {
+                    if (toType == TypedValue.TYPE_DIMENSION) {
+                        valueTo = (int) a.getDimension(valueToIndex, 0f);
+                    } else if ((toType >= TypedValue.TYPE_FIRST_COLOR_INT) &&
+                        (toType <= TypedValue.TYPE_LAST_COLOR_INT)) {
+                        valueTo = a.getColor(valueToIndex, 0);
+                    } else {
+                        valueTo = a.getInt(valueToIndex, 0);
+                    }
+                    anim.setIntValues(valueTo);
+                }
+            }
+        }
+
+        anim.setDuration(duration);
+        anim.setStartDelay(startDelay);
+
+        if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatCount)) {
+            anim.setRepeatCount(
+                    a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatCount, 0));
+        }
+        if (a.hasValue(/*com.android.internal.R.styleable.*/Animator_repeatMode)) {
+            anim.setRepeatMode(
+                    a.getInt(/*com.android.internal.R.styleable.*/Animator_repeatMode,
+                            ValueAnimator.RESTART));
+        }
+        //if (evaluator != null) {
+        //    anim.setEvaluator(evaluator);
+        //}
+
+        final int resID =
+                a.getResourceId(/*com.android.internal.R.styleable.*/Animator_interpolator, 0);
+        if (resID > 0) {
+            anim.setInterpolator(AnimationUtils.loadInterpolator(context, resID));
+        }
+        a.recycle();
+
+        return anim;
+    }
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorListenerAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link android.animation.Animator.AnimatorListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationCancel(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationEnd(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationRepeat(Animator animation) {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void onAnimationStart(Animator animation) {
+    }
+
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorSet.java
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class plays a set of {@link Animator} objects in the specified order. Animations
+ * can be set up to play together, in sequence, or after a specified delay.
+ *
+ * <p>There are two different approaches to adding animations to a <code>AnimatorSet</code>:
+ * either the {@link AnimatorSet#playTogether(Animator[]) playTogether()} or
+ * {@link AnimatorSet#playSequentially(Animator[]) playSequentially()} methods can be called to add
+ * a set of animations all at once, or the {@link AnimatorSet#play(Animator)} can be
+ * used in conjunction with methods in the {@link AnimatorSet.Builder Builder}
+ * class to add animations
+ * one by one.</p>
+ *
+ * <p>It is possible to set up a <code>AnimatorSet</code> with circular dependencies between
+ * its animations. For example, an animation a1 could be set up to start before animation a2, a2
+ * before a3, and a3 before a1. The results of this configuration are undefined, but will typically
+ * result in none of the affected animations being played. Because of this (and because
+ * circular dependencies do not make logical sense anyway), circular dependencies
+ * should be avoided, and the dependency flow of animations should only be in one direction.
+ */
+public final class AnimatorSet extends Animator {
+
+    /**
+     * Internal variables
+     * NOTE: This object implements the clone() method, making a deep copy of any referenced
+     * objects. As other non-trivial fields are added to this class, make sure to add logic
+     * to clone() to make deep copies of them.
+     */
+
+    /**
+     * Tracks animations currently being played, so that we know what to
+     * cancel or end when cancel() or end() is called on this AnimatorSet
+     */
+    private ArrayList<Animator> mPlayingSet = new ArrayList<Animator>();
+
+    /**
+     * Contains all nodes, mapped to their respective Animators. When new
+     * dependency information is added for an Animator, we want to add it
+     * to a single node representing that Animator, not create a new Node
+     * if one already exists.
+     */
+    private HashMap<Animator, Node> mNodeMap = new HashMap<Animator, Node>();
+
+    /**
+     * Set of all nodes created for this AnimatorSet. This list is used upon
+     * starting the set, and the nodes are placed in sorted order into the
+     * sortedNodes collection.
+     */
+    private ArrayList<Node> mNodes = new ArrayList<Node>();
+
+    /**
+     * The sorted list of nodes. This is the order in which the animations will
+     * be played. The details about when exactly they will be played depend
+     * on the dependency relationships of the nodes.
+     */
+    private ArrayList<Node> mSortedNodes = new ArrayList<Node>();
+
+    /**
+     * Flag indicating whether the nodes should be sorted prior to playing. This
+     * flag allows us to cache the previous sorted nodes so that if the sequence
+     * is replayed with no changes, it does not have to re-sort the nodes again.
+     */
+    private boolean mNeedsSort = true;
+
+    private AnimatorSetListener mSetListener = null;
+
+    /**
+     * Flag indicating that the AnimatorSet has been manually
+     * terminated (by calling cancel() or end()).
+     * This flag is used to avoid starting other animations when currently-playing
+     * child animations of this AnimatorSet end. It also determines whether cancel/end
+     * notifications are sent out via the normal AnimatorSetListener mechanism.
+     */
+    boolean mTerminated = false;
+
+    /**
+     * Indicates whether an AnimatorSet has been start()'d, whether or
+     * not there is a nonzero startDelay.
+     */
+    private boolean mStarted = false;
+
+    // The amount of time in ms to delay starting the animation after start() is called
+    private long mStartDelay = 0;
+
+    // Animator used for a nonzero startDelay
+    private ValueAnimator mDelayAnim = null;
+
+
+    // How long the child animations should last in ms. The default value is negative, which
+    // simply means that there is no duration set on the AnimatorSet. When a real duration is
+    // set, it is passed along to the child animations.
+    private long mDuration = -1;
+
+
+    /**
+     * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+     *
+     * @param items The animations that will be started simultaneously.
+     */
+    public void playTogether(Animator... items) {
+        if (items != null) {
+            mNeedsSort = true;
+            Builder builder = play(items[0]);
+            for (int i = 1; i < items.length; ++i) {
+                builder.with(items[i]);
+            }
+        }
+    }
+
+    /**
+     * Sets up this AnimatorSet to play all of the supplied animations at the same time.
+     *
+     * @param items The animations that will be started simultaneously.
+     */
+    public void playTogether(Collection<Animator> items) {
+        if (items != null && items.size() > 0) {
+            mNeedsSort = true;
+            Builder builder = null;
+            for (Animator anim : items) {
+                if (builder == null) {
+                    builder = play(anim);
+                } else {
+                    builder.with(anim);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets up this AnimatorSet to play each of the supplied animations when the
+     * previous animation ends.
+     *
+     * @param items The animations that will be started one after another.
+     */
+    public void playSequentially(Animator... items) {
+        if (items != null) {
+            mNeedsSort = true;
+            if (items.length == 1) {
+                play(items[0]);
+            } else {
+                for (int i = 0; i < items.length - 1; ++i) {
+                    play(items[i]).before(items[i+1]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Sets up this AnimatorSet to play each of the supplied animations when the
+     * previous animation ends.
+     *
+     * @param items The animations that will be started one after another.
+     */
+    public void playSequentially(List<Animator> items) {
+        if (items != null && items.size() > 0) {
+            mNeedsSort = true;
+            if (items.size() == 1) {
+                play(items.get(0));
+            } else {
+                for (int i = 0; i < items.size() - 1; ++i) {
+                    play(items.get(i)).before(items.get(i+1));
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the current list of child Animator objects controlled by this
+     * AnimatorSet. This is a copy of the internal list; modifications to the returned list
+     * will not affect the AnimatorSet, although changes to the underlying Animator objects
+     * will affect those objects being managed by the AnimatorSet.
+     *
+     * @return ArrayList<Animator> The list of child animations of this AnimatorSet.
+     */
+    public ArrayList<Animator> getChildAnimations() {
+        ArrayList<Animator> childList = new ArrayList<Animator>();
+        for (Node node : mNodes) {
+            childList.add(node.animation);
+        }
+        return childList;
+    }
+
+    /**
+     * Sets the target object for all current {@link #getChildAnimations() child animations}
+     * of this AnimatorSet that take targets ({@link ObjectAnimator} and
+     * AnimatorSet).
+     *
+     * @param target The object being animated
+     */
+    @Override
+    public void setTarget(Object target) {
+        for (Node node : mNodes) {
+            Animator animation = node.animation;
+            if (animation instanceof AnimatorSet) {
+                ((AnimatorSet)animation).setTarget(target);
+            } else if (animation instanceof ObjectAnimator) {
+                ((ObjectAnimator)animation).setTarget(target);
+            }
+        }
+    }
+
+    /**
+     * Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
+     * of this AnimatorSet.
+     *
+     * @param interpolator the interpolator to be used by each child animation of this AnimatorSet
+     */
+    @Override
+    public void setInterpolator(/*Time*/Interpolator interpolator) {
+        for (Node node : mNodes) {
+            node.animation.setInterpolator(interpolator);
+        }
+    }
+
+    /**
+     * This method creates a <code>Builder</code> object, which is used to
+     * set up playing constraints. This initial <code>play()</code> method
+     * tells the <code>Builder</code> the animation that is the dependency for
+     * the succeeding commands to the <code>Builder</code>. For example,
+     * calling <code>play(a1).with(a2)</code> sets up the AnimatorSet to play
+     * <code>a1</code> and <code>a2</code> at the same time,
+     * <code>play(a1).before(a2)</code> sets up the AnimatorSet to play
+     * <code>a1</code> first, followed by <code>a2</code>, and
+     * <code>play(a1).after(a2)</code> sets up the AnimatorSet to play
+     * <code>a2</code> first, followed by <code>a1</code>.
+     *
+     * <p>Note that <code>play()</code> is the only way to tell the
+     * <code>Builder</code> the animation upon which the dependency is created,
+     * so successive calls to the various functions in <code>Builder</code>
+     * will all refer to the initial parameter supplied in <code>play()</code>
+     * as the dependency of the other animations. For example, calling
+     * <code>play(a1).before(a2).before(a3)</code> will play both <code>a2</code>
+     * and <code>a3</code> when a1 ends; it does not set up a dependency between
+     * <code>a2</code> and <code>a3</code>.</p>
+     *
+     * @param anim The animation that is the dependency used in later calls to the
+     * methods in the returned <code>Builder</code> object. A null parameter will result
+     * in a null <code>Builder</code> return value.
+     * @return Builder The object that constructs the AnimatorSet based on the dependencies
+     * outlined in the calls to <code>play</code> and the other methods in the
+     * <code>Builder</code object.
+     */
+    public Builder play(Animator anim) {
+        if (anim != null) {
+            mNeedsSort = true;
+            return new Builder(anim);
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that canceling a <code>AnimatorSet</code> also cancels all of the animations that it
+     * is responsible for.</p>
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void cancel() {
+        mTerminated = true;
+        if (isStarted()) {
+            ArrayList<AnimatorListener> tmpListeners = null;
+            if (mListeners != null) {
+                tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
+                for (AnimatorListener listener : tmpListeners) {
+                    listener.onAnimationCancel(this);
+                }
+            }
+            if (mDelayAnim != null && mDelayAnim.isRunning()) {
+                // If we're currently in the startDelay period, just cancel that animator and
+                // send out the end event to all listeners
+                mDelayAnim.cancel();
+            } else  if (mSortedNodes.size() > 0) {
+                for (Node node : mSortedNodes) {
+                    node.animation.cancel();
+                }
+            }
+            if (tmpListeners != null) {
+                for (AnimatorListener listener : tmpListeners) {
+                    listener.onAnimationEnd(this);
+                }
+            }
+            mStarted = false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Note that ending a <code>AnimatorSet</code> also ends all of the animations that it is
+     * responsible for.</p>
+     */
+    @Override
+    public void end() {
+        mTerminated = true;
+        if (isStarted()) {
+            if (mSortedNodes.size() != mNodes.size()) {
+                // hasn't been started yet - sort the nodes now, then end them
+                sortNodes();
+                for (Node node : mSortedNodes) {
+                    if (mSetListener == null) {
+                        mSetListener = new AnimatorSetListener(this);
+                    }
+                    node.animation.addListener(mSetListener);
+                }
+            }
+            if (mDelayAnim != null) {
+                mDelayAnim.cancel();
+            }
+            if (mSortedNodes.size() > 0) {
+                for (Node node : mSortedNodes) {
+                    node.animation.end();
+                }
+            }
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                for (AnimatorListener listener : tmpListeners) {
+                    listener.onAnimationEnd(this);
+                }
+            }
+            mStarted = false;
+        }
+    }
+
+    /**
+     * Returns true if any of the child animations of this AnimatorSet have been started and have
+     * not yet ended.
+     * @return Whether this AnimatorSet has been started and has not yet ended.
+     */
+    @Override
+    public boolean isRunning() {
+        for (Node node : mNodes) {
+            if (node.animation.isRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mStarted;
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+     *
+     * @return the number of milliseconds to delay running the animation
+     */
+    @Override
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+
+     * @param startDelay The amount of the delay, in milliseconds
+     */
+    @Override
+    public void setStartDelay(long startDelay) {
+        mStartDelay = startDelay;
+    }
+
+    /**
+     * Gets the length of each of the child animations of this AnimatorSet. This value may
+     * be less than 0, which indicates that no duration has been set on this AnimatorSet
+     * and each of the child animations will use their own duration.
+     *
+     * @return The length of the animation, in milliseconds, of each of the child
+     * animations of this AnimatorSet.
+     */
+    @Override
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Sets the length of each of the current child animations of this AnimatorSet. By default,
+     * each child animation will use its own duration. If the duration is set on the AnimatorSet,
+     * then each child animation inherits this duration.
+     *
+     * @param duration The length of the animation, in milliseconds, of each of the child
+     * animations of this AnimatorSet.
+     */
+    @Override
+    public AnimatorSet setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("duration must be a value of zero or greater");
+        }
+        for (Node node : mNodes) {
+            // TODO: don't set the duration of the timing-only nodes created by AnimatorSet to
+            // insert "play-after" delays
+            node.animation.setDuration(duration);
+        }
+        mDuration = duration;
+        return this;
+    }
+
+    @Override
+    public void setupStartValues() {
+        for (Node node : mNodes) {
+            node.animation.setupStartValues();
+        }
+    }
+
+    @Override
+    public void setupEndValues() {
+        for (Node node : mNodes) {
+            node.animation.setupEndValues();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Starting this <code>AnimatorSet</code> will, in turn, start the animations for which
+     * it is responsible. The details of when exactly those animations are started depends on
+     * the dependency relationships that have been set up between the animations.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public void start() {
+        mTerminated = false;
+        mStarted = true;
+
+        // First, sort the nodes (if necessary). This will ensure that sortedNodes
+        // contains the animation nodes in the correct order.
+        sortNodes();
+
+        int numSortedNodes = mSortedNodes.size();
+        for (int i = 0; i < numSortedNodes; ++i) {
+            Node node = mSortedNodes.get(i);
+            // First, clear out the old listeners
+            ArrayList<AnimatorListener> oldListeners = node.animation.getListeners();
+            if (oldListeners != null && oldListeners.size() > 0) {
+                final ArrayList<AnimatorListener> clonedListeners = new
+                        ArrayList<AnimatorListener>(oldListeners);
+
+                for (AnimatorListener listener : clonedListeners) {
+                    if (listener instanceof DependencyListener ||
+                            listener instanceof AnimatorSetListener) {
+                        node.animation.removeListener(listener);
+                    }
+                }
+            }
+        }
+
+        // nodesToStart holds the list of nodes to be started immediately. We don't want to
+        // start the animations in the loop directly because we first need to set up
+        // dependencies on all of the nodes. For example, we don't want to start an animation
+        // when some other animation also wants to start when the first animation begins.
+        final ArrayList<Node> nodesToStart = new ArrayList<Node>();
+        for (int i = 0; i < numSortedNodes; ++i) {
+            Node node = mSortedNodes.get(i);
+            if (mSetListener == null) {
+                mSetListener = new AnimatorSetListener(this);
+            }
+            if (node.dependencies == null || node.dependencies.size() == 0) {
+                nodesToStart.add(node);
+            } else {
+                int numDependencies = node.dependencies.size();
+                for (int j = 0; j < numDependencies; ++j) {
+                    Dependency dependency = node.dependencies.get(j);
+                    dependency.node.animation.addListener(
+                            new DependencyListener(this, node, dependency.rule));
+                }
+                node.tmpDependencies = (ArrayList<Dependency>) node.dependencies.clone();
+            }
+            node.animation.addListener(mSetListener);
+        }
+        // Now that all dependencies are set up, start the animations that should be started.
+        if (mStartDelay <= 0) {
+            for (Node node : nodesToStart) {
+                node.animation.start();
+                mPlayingSet.add(node.animation);
+            }
+        } else {
+            mDelayAnim = ValueAnimator.ofFloat(0f, 1f);
+            mDelayAnim.setDuration(mStartDelay);
+            mDelayAnim.addListener(new AnimatorListenerAdapter() {
+                boolean canceled = false;
+                public void onAnimationCancel(Animator anim) {
+                    canceled = true;
+                }
+                public void onAnimationEnd(Animator anim) {
+                    if (!canceled) {
+                        int numNodes = nodesToStart.size();
+                        for (int i = 0; i < numNodes; ++i) {
+                            Node node = nodesToStart.get(i);
+                            node.animation.start();
+                            mPlayingSet.add(node.animation);
+                        }
+                    }
+                }
+            });
+            mDelayAnim.start();
+        }
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationStart(this);
+            }
+        }
+        if (mNodes.size() == 0 && mStartDelay == 0) {
+            // Handle unusual case where empty AnimatorSet is started - should send out
+            // end event immediately since the event will not be sent out at all otherwise
+            mStarted = false;
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationEnd(this);
+                }
+            }
+        }
+    }
+
+    @Override
+    public AnimatorSet clone() {
+        final AnimatorSet anim = (AnimatorSet) super.clone();
+        /*
+         * The basic clone() operation copies all items. This doesn't work very well for
+         * AnimatorSet, because it will copy references that need to be recreated and state
+         * that may not apply. What we need to do now is put the clone in an uninitialized
+         * state, with fresh, empty data structures. Then we will build up the nodes list
+         * manually, as we clone each Node (and its animation). The clone will then be sorted,
+         * and will populate any appropriate lists, when it is started.
+         */
+        anim.mNeedsSort = true;
+        anim.mTerminated = false;
+        anim.mStarted = false;
+        anim.mPlayingSet = new ArrayList<Animator>();
+        anim.mNodeMap = new HashMap<Animator, Node>();
+        anim.mNodes = new ArrayList<Node>();
+        anim.mSortedNodes = new ArrayList<Node>();
+
+        // Walk through the old nodes list, cloning each node and adding it to the new nodemap.
+        // One problem is that the old node dependencies point to nodes in the old AnimatorSet.
+        // We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
+        HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new>
+        for (Node node : mNodes) {
+            Node nodeClone = node.clone();
+            nodeCloneMap.put(node, nodeClone);
+            anim.mNodes.add(nodeClone);
+            anim.mNodeMap.put(nodeClone.animation, nodeClone);
+            // Clear out the dependencies in the clone; we'll set these up manually later
+            nodeClone.dependencies = null;
+            nodeClone.tmpDependencies = null;
+            nodeClone.nodeDependents = null;
+            nodeClone.nodeDependencies = null;
+            // clear out any listeners that were set up by the AnimatorSet; these will
+            // be set up when the clone's nodes are sorted
+            ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
+            if (cloneListeners != null) {
+                ArrayList<AnimatorListener> listenersToRemove = null;
+                for (AnimatorListener listener : cloneListeners) {
+                    if (listener instanceof AnimatorSetListener) {
+                        if (listenersToRemove == null) {
+                            listenersToRemove = new ArrayList<AnimatorListener>();
+                        }
+                        listenersToRemove.add(listener);
+                    }
+                }
+                if (listenersToRemove != null) {
+                    for (AnimatorListener listener : listenersToRemove) {
+                        cloneListeners.remove(listener);
+                    }
+                }
+            }
+        }
+        // Now that we've cloned all of the nodes, we're ready to walk through their
+        // dependencies, mapping the old dependencies to the new nodes
+        for (Node node : mNodes) {
+            Node nodeClone = nodeCloneMap.get(node);
+            if (node.dependencies != null) {
+                for (Dependency dependency : node.dependencies) {
+                    Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
+                    Dependency cloneDependency = new Dependency(clonedDependencyNode,
+                            dependency.rule);
+                    nodeClone.addDependency(cloneDependency);
+                }
+            }
+        }
+
+        return anim;
+    }
+
+    /**
+     * This class is the mechanism by which animations are started based on events in other
+     * animations. If an animation has multiple dependencies on other animations, then
+     * all dependencies must be satisfied before the animation is started.
+     */
+    private static class DependencyListener implements AnimatorListener {
+
+        private AnimatorSet mAnimatorSet;
+
+        // The node upon which the dependency is based.
+        private Node mNode;
+
+        // The Dependency rule (WITH or AFTER) that the listener should wait for on
+        // the node
+        private int mRule;
+
+        public DependencyListener(AnimatorSet animatorSet, Node node, int rule) {
+            this.mAnimatorSet = animatorSet;
+            this.mNode = node;
+            this.mRule = rule;
+        }
+
+        /**
+         * Ignore cancel events for now. We may want to handle this eventually,
+         * to prevent follow-on animations from running when some dependency
+         * animation is canceled.
+         */
+        public void onAnimationCancel(Animator animation) {
+        }
+
+        /**
+         * An end event is received - see if this is an event we are listening for
+         */
+        public void onAnimationEnd(Animator animation) {
+            if (mRule == Dependency.AFTER) {
+                startIfReady(animation);
+            }
+        }
+
+        /**
+         * Ignore repeat events for now
+         */
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        /**
+         * A start event is received - see if this is an event we are listening for
+         */
+        public void onAnimationStart(Animator animation) {
+            if (mRule == Dependency.WITH) {
+                startIfReady(animation);
+            }
+        }
+
+        /**
+         * Check whether the event received is one that the node was waiting for.
+         * If so, mark it as complete and see whether it's time to start
+         * the animation.
+         * @param dependencyAnimation the animation that sent the event.
+         */
+        private void startIfReady(Animator dependencyAnimation) {
+            if (mAnimatorSet.mTerminated) {
+                // if the parent AnimatorSet was canceled, then don't start any dependent anims
+                return;
+            }
+            Dependency dependencyToRemove = null;
+            int numDependencies = mNode.tmpDependencies.size();
+            for (int i = 0; i < numDependencies; ++i) {
+                Dependency dependency = mNode.tmpDependencies.get(i);
+                if (dependency.rule == mRule &&
+                        dependency.node.animation == dependencyAnimation) {
+                    // rule fired - remove the dependency and listener and check to
+                    // see whether it's time to start the animation
+                    dependencyToRemove = dependency;
+                    dependencyAnimation.removeListener(this);
+                    break;
+                }
+            }
+            mNode.tmpDependencies.remove(dependencyToRemove);
+            if (mNode.tmpDependencies.size() == 0) {
+                // all dependencies satisfied: start the animation
+                mNode.animation.start();
+                mAnimatorSet.mPlayingSet.add(mNode.animation);
+            }
+        }
+
+    }
+
+    private class AnimatorSetListener implements AnimatorListener {
+
+        private AnimatorSet mAnimatorSet;
+
+        AnimatorSetListener(AnimatorSet animatorSet) {
+            mAnimatorSet = animatorSet;
+        }
+
+        public void onAnimationCancel(Animator animation) {
+            if (!mTerminated) {
+                // Listeners are already notified of the AnimatorSet canceling in cancel().
+                // The logic below only kicks in when animations end normally
+                if (mPlayingSet.size() == 0) {
+                    if (mListeners != null) {
+                        int numListeners = mListeners.size();
+                        for (int i = 0; i < numListeners; ++i) {
+                            mListeners.get(i).onAnimationCancel(mAnimatorSet);
+                        }
+                    }
+                }
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        public void onAnimationEnd(Animator animation) {
+            animation.removeListener(this);
+            mPlayingSet.remove(animation);
+            Node animNode = mAnimatorSet.mNodeMap.get(animation);
+            animNode.done = true;
+            if (!mTerminated) {
+                // Listeners are already notified of the AnimatorSet ending in cancel() or
+                // end(); the logic below only kicks in when animations end normally
+                ArrayList<Node> sortedNodes = mAnimatorSet.mSortedNodes;
+                boolean allDone = true;
+                int numSortedNodes = sortedNodes.size();
+                for (int i = 0; i < numSortedNodes; ++i) {
+                    if (!sortedNodes.get(i).done) {
+                        allDone = false;
+                        break;
+                    }
+                }
+                if (allDone) {
+                    // If this was the last child animation to end, then notify listeners that this
+                    // AnimatorSet has ended
+                    if (mListeners != null) {
+                        ArrayList<AnimatorListener> tmpListeners =
+                                (ArrayList<AnimatorListener>) mListeners.clone();
+                        int numListeners = tmpListeners.size();
+                        for (int i = 0; i < numListeners; ++i) {
+                            tmpListeners.get(i).onAnimationEnd(mAnimatorSet);
+                        }
+                    }
+                    mAnimatorSet.mStarted = false;
+                }
+            }
+        }
+
+        // Nothing to do
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        // Nothing to do
+        public void onAnimationStart(Animator animation) {
+        }
+
+    }
+
+    /**
+     * This method sorts the current set of nodes, if needed. The sort is a simple
+     * DependencyGraph sort, which goes like this:
+     * - All nodes without dependencies become 'roots'
+     * - while roots list is not null
+     * -   for each root r
+     * -     add r to sorted list
+     * -     remove r as a dependency from any other node
+     * -   any nodes with no dependencies are added to the roots list
+     */
+    private void sortNodes() {
+        if (mNeedsSort) {
+            mSortedNodes.clear();
+            ArrayList<Node> roots = new ArrayList<Node>();
+            int numNodes = mNodes.size();
+            for (int i = 0; i < numNodes; ++i) {
+                Node node = mNodes.get(i);
+                if (node.dependencies == null || node.dependencies.size() == 0) {
+                    roots.add(node);
+                }
+            }
+            ArrayList<Node> tmpRoots = new ArrayList<Node>();
+            while (roots.size() > 0) {
+                int numRoots = roots.size();
+                for (int i = 0; i < numRoots; ++i) {
+                    Node root = roots.get(i);
+                    mSortedNodes.add(root);
+                    if (root.nodeDependents != null) {
+                        int numDependents = root.nodeDependents.size();
+                        for (int j = 0; j < numDependents; ++j) {
+                            Node node = root.nodeDependents.get(j);
+                            node.nodeDependencies.remove(root);
+                            if (node.nodeDependencies.size() == 0) {
+                                tmpRoots.add(node);
+                            }
+                        }
+                    }
+                }
+                roots.clear();
+                roots.addAll(tmpRoots);
+                tmpRoots.clear();
+            }
+            mNeedsSort = false;
+            if (mSortedNodes.size() != mNodes.size()) {
+                throw new IllegalStateException("Circular dependencies cannot exist"
+                        + " in AnimatorSet");
+            }
+        } else {
+            // Doesn't need sorting, but still need to add in the nodeDependencies list
+            // because these get removed as the event listeners fire and the dependencies
+            // are satisfied
+            int numNodes = mNodes.size();
+            for (int i = 0; i < numNodes; ++i) {
+                Node node = mNodes.get(i);
+                if (node.dependencies != null && node.dependencies.size() > 0) {
+                    int numDependencies = node.dependencies.size();
+                    for (int j = 0; j < numDependencies; ++j) {
+                        Dependency dependency = node.dependencies.get(j);
+                        if (node.nodeDependencies == null) {
+                            node.nodeDependencies = new ArrayList<Node>();
+                        }
+                        if (!node.nodeDependencies.contains(dependency.node)) {
+                            node.nodeDependencies.add(dependency.node);
+                        }
+                    }
+                }
+                // nodes are 'done' by default; they become un-done when started, and done
+                // again when ended
+                node.done = false;
+            }
+        }
+    }
+
+    /**
+     * Dependency holds information about the node that some other node is
+     * dependent upon and the nature of that dependency.
+     *
+     */
+    private static class Dependency {
+        static final int WITH = 0; // dependent node must start with this dependency node
+        static final int AFTER = 1; // dependent node must start when this dependency node finishes
+
+        // The node that the other node with this Dependency is dependent upon
+        public Node node;
+
+        // The nature of the dependency (WITH or AFTER)
+        public int rule;
+
+        public Dependency(Node node, int rule) {
+            this.node = node;
+            this.rule = rule;
+        }
+    }
+
+    /**
+     * A Node is an embodiment of both the Animator that it wraps as well as
+     * any dependencies that are associated with that Animation. This includes
+     * both dependencies upon other nodes (in the dependencies list) as
+     * well as dependencies of other nodes upon this (in the nodeDependents list).
+     */
+    private static class Node implements Cloneable {
+        public Animator animation;
+
+        /**
+         *  These are the dependencies that this node's animation has on other
+         *  nodes. For example, if this node's animation should begin with some
+         *  other animation ends, then there will be an item in this node's
+         *  dependencies list for that other animation's node.
+         */
+        public ArrayList<Dependency> dependencies = null;
+
+        /**
+         * tmpDependencies is a runtime detail. We use the dependencies list for sorting.
+         * But we also use the list to keep track of when multiple dependencies are satisfied,
+         * but removing each dependency as it is satisfied. We do not want to remove
+         * the dependency itself from the list, because we need to retain that information
+         * if the AnimatorSet is launched in the future. So we create a copy of the dependency
+         * list when the AnimatorSet starts and use this tmpDependencies list to track the
+         * list of satisfied dependencies.
+         */
+        public ArrayList<Dependency> tmpDependencies = null;
+
+        /**
+         * nodeDependencies is just a list of the nodes that this Node is dependent upon.
+         * This information is used in sortNodes(), to determine when a node is a root.
+         */
+        public ArrayList<Node> nodeDependencies = null;
+
+        /**
+         * nodeDepdendents is the list of nodes that have this node as a dependency. This
+         * is a utility field used in sortNodes to facilitate removing this node as a
+         * dependency when it is a root node.
+         */
+        public ArrayList<Node> nodeDependents = null;
+
+        /**
+         * Flag indicating whether the animation in this node is finished. This flag
+         * is used by AnimatorSet to check, as each animation ends, whether all child animations
+         * are done and it's time to send out an end event for the entire AnimatorSet.
+         */
+        public boolean done = false;
+
+        /**
+         * Constructs the Node with the animation that it encapsulates. A Node has no
+         * dependencies by default; dependencies are added via the addDependency()
+         * method.
+         *
+         * @param animation The animation that the Node encapsulates.
+         */
+        public Node(Animator animation) {
+            this.animation = animation;
+        }
+
+        /**
+         * Add a dependency to this Node. The dependency includes information about the
+         * node that this node is dependency upon and the nature of the dependency.
+         * @param dependency
+         */
+        public void addDependency(Dependency dependency) {
+            if (dependencies == null) {
+                dependencies = new ArrayList<Dependency>();
+                nodeDependencies = new ArrayList<Node>();
+            }
+            dependencies.add(dependency);
+            if (!nodeDependencies.contains(dependency.node)) {
+                nodeDependencies.add(dependency.node);
+            }
+            Node dependencyNode = dependency.node;
+            if (dependencyNode.nodeDependents == null) {
+                dependencyNode.nodeDependents = new ArrayList<Node>();
+            }
+            dependencyNode.nodeDependents.add(this);
+        }
+
+        @Override
+        public Node clone() {
+            try {
+                Node node = (Node) super.clone();
+                node.animation = (Animator) animation.clone();
+                return node;
+            } catch (CloneNotSupportedException e) {
+               throw new AssertionError();
+            }
+        }
+    }
+
+    /**
+     * The <code>Builder</code> object is a utility class to facilitate adding animations to a
+     * <code>AnimatorSet</code> along with the relationships between the various animations. The
+     * intention of the <code>Builder</code> methods, along with the {@link
+     * AnimatorSet#play(Animator) play()} method of <code>AnimatorSet</code> is to make it possible
+     * to express the dependency relationships of animations in a natural way. Developers can also
+     * use the {@link AnimatorSet#playTogether(Animator[]) playTogether()} and {@link
+     * AnimatorSet#playSequentially(Animator[]) playSequentially()} methods if these suit the need,
+     * but it might be easier in some situations to express the AnimatorSet of animations in pairs.
+     * <p/>
+     * <p>The <code>Builder</code> object cannot be constructed directly, but is rather constructed
+     * internally via a call to {@link AnimatorSet#play(Animator)}.</p>
+     * <p/>
+     * <p>For example, this sets up a AnimatorSet to play anim1 and anim2 at the same time, anim3 to
+     * play when anim2 finishes, and anim4 to play when anim3 finishes:</p>
+     * <pre>
+     *     AnimatorSet s = new AnimatorSet();
+     *     s.play(anim1).with(anim2);
+     *     s.play(anim2).before(anim3);
+     *     s.play(anim4).after(anim3);
+     * </pre>
+     * <p/>
+     * <p>Note in the example that both {@link Builder#before(Animator)} and {@link
+     * Builder#after(Animator)} are used. These are just different ways of expressing the same
+     * relationship and are provided to make it easier to say things in a way that is more natural,
+     * depending on the situation.</p>
+     * <p/>
+     * <p>It is possible to make several calls into the same <code>Builder</code> object to express
+     * multiple relationships. However, note that it is only the animation passed into the initial
+     * {@link AnimatorSet#play(Animator)} method that is the dependency in any of the successive
+     * calls to the <code>Builder</code> object. For example, the following code starts both anim2
+     * and anim3 when anim1 ends; there is no direct dependency relationship between anim2 and
+     * anim3:
+     * <pre>
+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2).before(anim3);
+     * </pre>
+     * If the desired result is to play anim1 then anim2 then anim3, this code expresses the
+     * relationship correctly:</p>
+     * <pre>
+     *   AnimatorSet s = new AnimatorSet();
+     *   s.play(anim1).before(anim2);
+     *   s.play(anim2).before(anim3);
+     * </pre>
+     * <p/>
+     * <p>Note that it is possible to express relationships that cannot be resolved and will not
+     * result in sensible results. For example, <code>play(anim1).after(anim1)</code> makes no
+     * sense. In general, circular dependencies like this one (or more indirect ones where a depends
+     * on b, which depends on c, which depends on a) should be avoided. Only create AnimatorSets
+     * that can boil down to a simple, one-way relationship of animations starting with, before, and
+     * after other, different, animations.</p>
+     */
+    public class Builder {
+
+        /**
+         * This tracks the current node being processed. It is supplied to the play() method
+         * of AnimatorSet and passed into the constructor of Builder.
+         */
+        private Node mCurrentNode;
+
+        /**
+         * package-private constructor. Builders are only constructed by AnimatorSet, when the
+         * play() method is called.
+         *
+         * @param anim The animation that is the dependency for the other animations passed into
+         * the other methods of this Builder object.
+         */
+        Builder(Animator anim) {
+            mCurrentNode = mNodeMap.get(anim);
+            if (mCurrentNode == null) {
+                mCurrentNode = new Node(anim);
+                mNodeMap.put(anim, mCurrentNode);
+                mNodes.add(mCurrentNode);
+            }
+        }
+
+        /**
+         * Sets up the given animation to play at the same time as the animation supplied in the
+         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link AnimatorSet#play(Animator)} method starts.
+         */
+        public Builder with(Animator anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
+            node.addDependency(dependency);
+            return this;
+        }
+
+        /**
+         * Sets up the given animation to play when the animation supplied in the
+         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+         * ends.
+         *
+         * @param anim The animation that will play when the animation supplied to the
+         * {@link AnimatorSet#play(Animator)} method ends.
+         */
+        public Builder before(Animator anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
+            node.addDependency(dependency);
+            return this;
+        }
+
+        /**
+         * Sets up the given animation to play when the animation supplied in the
+         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+         * to start when the animation supplied in this method call ends.
+         *
+         * @param anim The animation whose end will cause the animation supplied to the
+         * {@link AnimatorSet#play(Animator)} method to play.
+         */
+        public Builder after(Animator anim) {
+            Node node = mNodeMap.get(anim);
+            if (node == null) {
+                node = new Node(anim);
+                mNodeMap.put(anim, node);
+                mNodes.add(node);
+            }
+            Dependency dependency = new Dependency(node, Dependency.AFTER);
+            mCurrentNode.addDependency(dependency);
+            return this;
+        }
+
+        /**
+         * Sets up the animation supplied in the
+         * {@link AnimatorSet#play(Animator)} call that created this <code>Builder</code> object
+         * to play when the given amount of time elapses.
+         *
+         * @param delay The number of milliseconds that should elapse before the
+         * animation starts.
+         */
+        public Builder after(long delay) {
+            // setup dummy ValueAnimator just to run the clock
+            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+            anim.setDuration(delay);
+            after(anim);
+            return this;
+        }
+
+    }
+
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ArgbEvaluator.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between integer
+ * values that represent ARGB colors.
+ */
+public class ArgbEvaluator implements TypeEvaluator {
+
+    /**
+     * This function returns the calculated in-between value for a color
+     * given integers that represent the start and end values in the four
+     * bytes of the 32-bit int. Each channel is separately linearly interpolated
+     * and the resulting calculated values are recombined into the return value.
+     *
+     * @param fraction The fraction from the starting to the ending values
+     * @param startValue A 32-bit int value representing colors in the
+     * separate bytes of the parameter
+     * @param endValue A 32-bit int value representing colors in the
+     * separate bytes of the parameter
+     * @return A value that is calculated to be the linearly interpolated
+     * result, derived by separating the start and end values into separate
+     * color channels and interpolating each one separately, recombining the
+     * resulting values in the same way.
+     */
+    public Object evaluate(float fraction, Object startValue, Object endValue) {
+        int startInt = (Integer) startValue;
+        int startA = (startInt >> 24);
+        int startR = (startInt >> 16) & 0xff;
+        int startG = (startInt >> 8) & 0xff;
+        int startB = startInt & 0xff;
+
+        int endInt = (Integer) endValue;
+        int endA = (endInt >> 24);
+        int endR = (endInt >> 16) & 0xff;
+        int endG = (endInt >> 8) & 0xff;
+        int endB = endInt & 0xff;
+
+        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
+                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
+                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
+                (int)((startB + (int)(fraction * (endB - startB))));
+    }
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/FloatEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float</code> values.
+ */
+public class FloatEvaluator implements TypeEvaluator<Number> {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value; should be of type <code>float</code> or
+     *                   <code>Float</code>
+     * @param endValue   The end value; should be of type <code>float</code> or <code>Float</code>
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Float evaluate(float fraction, Number startValue, Number endValue) {
+        float startFloat = startValue.floatValue();
+        return startFloat + fraction * (endValue.floatValue() - startFloat);
+    }
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/FloatKeyframeSet.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import android.view.animation.Interpolator;
+
+import com.nineoldandroids.animation.Keyframe.FloatKeyframe;
+
+import java.util.ArrayList;
+
+/**
+ * This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * int, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class FloatKeyframeSet extends KeyframeSet {
+    private float firstValue;
+    private float lastValue;
+    private float deltaValue;
+    private boolean firstTime = true;
+
+    public FloatKeyframeSet(FloatKeyframe... keyframes) {
+        super(keyframes);
+    }
+
+    @Override
+    public Object getValue(float fraction) {
+        return getFloatValue(fraction);
+    }
+
+    @Override
+    public FloatKeyframeSet clone() {
+        ArrayList<Keyframe> keyframes = mKeyframes;
+        int numKeyframes = mKeyframes.size();
+        FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
+        for (int i = 0; i < numKeyframes; ++i) {
+            newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
+        }
+        FloatKeyframeSet newSet = new FloatKeyframeSet(newKeyframes);
+        return newSet;
+    }
+
+    public float getFloatValue(float fraction) {
+        if (mNumKeyframes == 2) {
+            if (firstTime) {
+                firstTime = false;
+                firstValue = ((FloatKeyframe) mKeyframes.get(0)).getFloatValue();
+                lastValue = ((FloatKeyframe) mKeyframes.get(1)).getFloatValue();
+                deltaValue = lastValue - firstValue;
+            }
+            if (mInterpolator != null) {
+                fraction = mInterpolator.getInterpolation(fraction);
+            }
+            if (mEvaluator == null) {
+                return firstValue + fraction * deltaValue;
+            } else {
+                return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).floatValue();
+            }
+        }
+        if (fraction <= 0f) {
+            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
+            float prevValue = prevKeyframe.getFloatValue();
+            float nextValue = nextKeyframe.getFloatValue();
+            float prevFraction = prevKeyframe.getFraction();
+            float nextFraction = nextKeyframe.getFraction();
+            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+            return mEvaluator == null ?
+                    prevValue + intervalFraction * (nextValue - prevValue) :
+                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+                            floatValue();
+        } else if (fraction >= 1f) {
+            final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
+            final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
+            float prevValue = prevKeyframe.getFloatValue();
+            float nextValue = nextKeyframe.getFloatValue();
+            float prevFraction = prevKeyframe.getFraction();
+            float nextFraction = nextKeyframe.getFraction();
+            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+            return mEvaluator == null ?
+                    prevValue + intervalFraction * (nextValue - prevValue) :
+                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+                            floatValue();
+        }
+        FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
+        for (int i = 1; i < mNumKeyframes; ++i) {
+            FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
+            if (fraction < nextKeyframe.getFraction()) {
+                final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+                if (interpolator != null) {
+                    fraction = interpolator.getInterpolation(fraction);
+                }
+                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+                float prevValue = prevKeyframe.getFloatValue();
+                float nextValue = nextKeyframe.getFloatValue();
+                return mEvaluator == null ?
+                        prevValue + intervalFraction * (nextValue - prevValue) :
+                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+                            floatValue();
+            }
+            prevKeyframe = nextKeyframe;
+        }
+        // shouldn't get here
+        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
+    }
+
+}
+
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/IntEvaluator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>int</code> values.
+ */
+public class IntEvaluator implements TypeEvaluator<Integer> {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value; should be of type <code>int</code> or
+     *                   <code>Integer</code>
+     * @param endValue   The end value; should be of type <code>int</code> or <code>Integer</code>
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
+        int startInt = startValue;
+        return (int)(startInt + fraction * (endValue - startInt));
+    }
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/IntKeyframeSet.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import android.view.animation.Interpolator;
+
+import com.nineoldandroids.animation.Keyframe.IntKeyframe;
+
+import java.util.ArrayList;
+
+/**
+ * This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ *
+ * <p>This type-specific subclass of KeyframeSet, along with the other type-specific subclass for
+ * float, exists to speed up the getValue() method when there is no custom
+ * TypeEvaluator set for the animation, so that values can be calculated without autoboxing to the
+ * Object equivalents of these primitive types.</p>
+ */
+class IntKeyframeSet extends KeyframeSet {
+    private int firstValue;
+    private int lastValue;
+    private int deltaValue;
+    private boolean firstTime = true;
+
+    public IntKeyframeSet(IntKeyframe... keyframes) {
+        super(keyframes);
+    }
+
+    @Override
+    public Object getValue(float fraction) {
+        return getIntValue(fraction);
+    }
+
+    @Override
+    public IntKeyframeSet clone() {
+        ArrayList<Keyframe> keyframes = mKeyframes;
+        int numKeyframes = mKeyframes.size();
+        IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
+        for (int i = 0; i < numKeyframes; ++i) {
+            newKeyframes[i] = (IntKeyframe) keyframes.get(i).clone();
+        }
+        IntKeyframeSet newSet = new IntKeyframeSet(newKeyframes);
+        return newSet;
+    }
+
+    public int getIntValue(float fraction) {
+        if (mNumKeyframes == 2) {
+            if (firstTime) {
+                firstTime = false;
+                firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue();
+                lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue();
+                deltaValue = lastValue - firstValue;
+            }
+            if (mInterpolator != null) {
+                fraction = mInterpolator.getInterpolation(fraction);
+            }
+            if (mEvaluator == null) {
+                return firstValue + (int)(fraction * deltaValue);
+            } else {
+                return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
+            }
+        }
+        if (fraction <= 0f) {
+            final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+            final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
+            int prevValue = prevKeyframe.getIntValue();
+            int nextValue = nextKeyframe.getIntValue();
+            float prevFraction = prevKeyframe.getFraction();
+            float nextFraction = nextKeyframe.getFraction();
+            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+            return mEvaluator == null ?
+                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+                            intValue();
+        } else if (fraction >= 1f) {
+            final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
+            final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
+            int prevValue = prevKeyframe.getIntValue();
+            int nextValue = nextKeyframe.getIntValue();
+            float prevFraction = prevKeyframe.getFraction();
+            float nextFraction = nextKeyframe.getFraction();
+            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
+            return mEvaluator == null ?
+                    prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
+        }
+        IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
+        for (int i = 1; i < mNumKeyframes; ++i) {
+            IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
+            if (fraction < nextKeyframe.getFraction()) {
+                final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+                if (interpolator != null) {
+                    fraction = interpolator.getInterpolation(fraction);
+                }
+                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
+                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
+                int prevValue = prevKeyframe.getIntValue();
+                int nextValue = nextKeyframe.getIntValue();
+                return mEvaluator == null ?
+                        prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
+                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
+                                intValue();
+            }
+            prevKeyframe = nextKeyframe;
+        }
+        // shouldn't get here
+        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
+    }
+
+}
+
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/Keyframe.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import android.view.animation.Interpolator;
+
+/**
+ * This class holds a time/value pair for an animation. The Keyframe class is used
+ * by {@link ValueAnimator} to define the values that the animation target will have over the course
+ * of the animation. As the time proceeds from one keyframe to the other, the value of the
+ * target object will animate between the value at the previous keyframe and the value at the
+ * next keyframe. Each keyframe also holds an optional {@link TimeInterpolator}
+ * object, which defines the time interpolation over the intervalue preceding the keyframe.
+ *
+ * <p>The Keyframe class itself is abstract. The type-specific factory methods will return
+ * a subclass of Keyframe specific to the type of value being stored. This is done to improve
+ * performance when dealing with the most common cases (e.g., <code>float</code> and
+ * <code>int</code> values). Other types will fall into a more general Keyframe class that
+ * treats its values as Objects. Unless your animation requires dealing with a custom type
+ * or a data structure that needs to be animated directly (and evaluated using an implementation
+ * of {@link TypeEvaluator}), you should stick to using float and int as animations using those
+ * types have lower runtime overhead than other types.</p>
+ */
+public abstract class Keyframe implements Cloneable {
+    /**
+     * The time at which mValue will hold true.
+     */
+    float mFraction;
+
+    /**
+     * The type of the value in this Keyframe. This type is determined at construction time,
+     * based on the type of the <code>value</code> object passed into the constructor.
+     */
+    Class mValueType;
+
+    /**
+     * The optional time interpolator for the interval preceding this keyframe. A null interpolator
+     * (the default) results in linear interpolation over the interval.
+     */
+    private /*Time*/Interpolator mInterpolator = null;
+
+    /**
+     * Flag to indicate whether this keyframe has a valid value. This flag is used when an
+     * animation first starts, to populate placeholder keyframes with real values derived
+     * from the target object.
+     */
+    boolean mHasValue = false;
+
+    /**
+     * Constructs a Keyframe object with the given time and value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public static Keyframe ofInt(float fraction, int value) {
+        return new IntKeyframe(fraction, value);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time. The value at this time will be derived
+     * from the target object when the animation first starts (note that this implies that keyframes
+     * with no initial value must be used as part of an {@link ObjectAnimator}).
+     * The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     */
+    public static Keyframe ofInt(float fraction) {
+        return new IntKeyframe(fraction);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public static Keyframe ofFloat(float fraction, float value) {
+        return new FloatKeyframe(fraction, value);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time. The value at this time will be derived
+     * from the target object when the animation first starts (note that this implies that keyframes
+     * with no initial value must be used as part of an {@link ObjectAnimator}).
+     * The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     */
+    public static Keyframe ofFloat(float fraction) {
+        return new FloatKeyframe(fraction);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time and value. The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     * @param value The value that the object will animate to as the animation time approaches
+     * the time in this keyframe, and the the value animated from as the time passes the time in
+     * this keyframe.
+     */
+    public static Keyframe ofObject(float fraction, Object value) {
+        return new ObjectKeyframe(fraction, value);
+    }
+
+    /**
+     * Constructs a Keyframe object with the given time. The value at this time will be derived
+     * from the target object when the animation first starts (note that this implies that keyframes
+     * with no initial value must be used as part of an {@link ObjectAnimator}).
+     * The time defines the
+     * time, as a proportion of an overall animation's duration, at which the value will hold true
+     * for the animation. The value for the animation between keyframes will be calculated as
+     * an interpolation between the values at those keyframes.
+     *
+     * @param fraction The time, expressed as a value between 0 and 1, representing the fraction
+     * of time elapsed of the overall animation duration.
+     */
+    public static Keyframe ofObject(float fraction) {
+        return new ObjectKeyframe(fraction, null);
+    }
+
+    /**
+     * Indicates whether this keyframe has a valid value. This method is called internally when
+     * an {@link ObjectAnimator} first starts; keyframes without values are assigned values at
+     * that time by deriving the value for the property from the target object.
+     *
+     * @return boolean Whether this object has a value assigned.
+     */
+    public boolean hasValue() {
+        return mHasValue;
+    }
+
+    /**
+     * Gets the value for this Keyframe.
+     *
+     * @return The value for this Keyframe.
+     */
+    public abstract Object getValue();
+
+    /**
+     * Sets the value for this Keyframe.
+     *
+     * @param value value for this Keyframe.
+     */
+    public abstract void setValue(Object value);
+
+    /**
+     * Gets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @return The time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public float getFraction() {
+        return mFraction;
+    }
+
+    /**
+     * Sets the time for this keyframe, as a fraction of the overall animation duration.
+     *
+     * @param fraction time associated with this keyframe, as a fraction of the overall animation
+     * duration. This should be a value between 0 and 1.
+     */
+    public void setFraction(float fraction) {
+        mFraction = fraction;
+    }
+
+    /**
+     * Gets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public /*Time*/Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Sets the optional interpolator for this Keyframe. A value of <code>null</code> indicates
+     * that there is no interpolation, which is the same as linear interpolation.
+     *
+     * @return The optional interpolator for this Keyframe.
+     */
+    public void setInterpolator(/*Time*/Interpolator interpolator) {
+        mInterpolator = interpolator;
+    }
+
+    /**
+     * Gets the type of keyframe. This information is used by ValueAnimator to determine the type of
+     * {@link TypeEvaluator} to use when calculating values between keyframes. The type is based
+     * on the type of Keyframe created.
+     *
+     * @return The type of the value stored in the Keyframe.
+     */
+    public Class getType() {
+        return mValueType;
+    }
+
+    @Override
+    public abstract Keyframe clone();
+
+    /**
+     * This internal subclass is used for all types which are not int or float.
+     */
+    static class ObjectKeyframe extends Keyframe {
+
+        /**
+         * The value of the animation at the time mFraction.
+         */
+        Object mValue;
+
+        ObjectKeyframe(float fraction, Object value) {
+            mFraction = fraction;
+            mValue = value;
+            mHasValue = (value != null);
+            mValueType = mHasValue ? value.getClass() : Object.class;
+        }
+
+        public Object getValue() {
+            return mValue;
+        }
+
+        public void setValue(Object value) {
+            mValue = value;
+            mHasValue = (value != null);
+        }
+
+        @Override
+        public ObjectKeyframe clone() {
+            ObjectKeyframe kfClone = new ObjectKeyframe(getFraction(), mValue);
+            kfClone.setInterpolator(getInterpolator());
+            return kfClone;
+        }
+    }
+
+    /**
+     * Internal subclass used when the keyframe value is of type int.
+     */
+    static class IntKeyframe extends Keyframe {
+
+        /**
+         * The value of the animation at the time mFraction.
+         */
+        int mValue;
+
+        IntKeyframe(float fraction, int value) {
+            mFraction = fraction;
+            mValue = value;
+            mValueType = int.class;
+            mHasValue = true;
+        }
+
+        IntKeyframe(float fraction) {
+            mFraction = fraction;
+            mValueType = int.class;
+        }
+
+        public int getIntValue() {
+            return mValue;
+        }
+
+        public Object getValue() {
+            return mValue;
+        }
+
+        public void setValue(Object value) {
+            if (value != null && value.getClass() == Integer.class) {
+                mValue = ((Integer)value).intValue();
+                mHasValue = true;
+            }
+        }
+
+        @Override
+        public IntKeyframe clone() {
+            IntKeyframe kfClone = new IntKeyframe(getFraction(), mValue);
+            kfClone.setInterpolator(getInterpolator());
+            return kfClone;
+        }
+    }
+
+    /**
+     * Internal subclass used when the keyframe value is of type float.
+     */
+    static class FloatKeyframe extends Keyframe {
+        /**
+         * The value of the animation at the time mFraction.
+         */
+        float mValue;
+
+        FloatKeyframe(float fraction, float value) {
+            mFraction = fraction;
+            mValue = value;
+            mValueType = float.class;
+            mHasValue = true;
+        }
+
+        FloatKeyframe(float fraction) {
+            mFraction = fraction;
+            mValueType = float.class;
+        }
+
+        public float getFloatValue() {
+            return mValue;
+        }
+
+        public Object getValue() {
+            return mValue;
+        }
+
+        public void setValue(Object value) {
+            if (value != null && value.getClass() == Float.class) {
+                mValue = ((Float)value).floatValue();
+                mHasValue = true;
+            }
+        }
+
+        @Override
+        public FloatKeyframe clone() {
+            FloatKeyframe kfClone = new FloatKeyframe(getFraction(), mValue);
+            kfClone.setInterpolator(getInterpolator());
+            return kfClone;
+        }
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/KeyframeSet.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import android.view.animation.Interpolator;
+
+import com.nineoldandroids.animation.Keyframe.FloatKeyframe;
+import com.nineoldandroids.animation.Keyframe.IntKeyframe;
+import com.nineoldandroids.animation.Keyframe.ObjectKeyframe;
+
+/**
+ * This class holds a collection of Keyframe objects and is called by ValueAnimator to calculate
+ * values between those keyframes for a given animation. The class internal to the animation
+ * package because it is an implementation detail of how Keyframes are stored and used.
+ */
+class KeyframeSet {
+
+    int mNumKeyframes;
+
+    Keyframe mFirstKeyframe;
+    Keyframe mLastKeyframe;
+    /*Time*/Interpolator mInterpolator; // only used in the 2-keyframe case
+    ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+    TypeEvaluator mEvaluator;
+
+
+    public KeyframeSet(Keyframe... keyframes) {
+        mNumKeyframes = keyframes.length;
+        mKeyframes = new ArrayList<Keyframe>();
+        mKeyframes.addAll(Arrays.asList(keyframes));
+        mFirstKeyframe = mKeyframes.get(0);
+        mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
+        mInterpolator = mLastKeyframe.getInterpolator();
+    }
+
+    public static KeyframeSet ofInt(int... values) {
+        int numKeyframes = values.length;
+        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
+        if (numKeyframes == 1) {
+            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
+            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
+        } else {
+            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
+            for (int i = 1; i < numKeyframes; ++i) {
+                keyframes[i] = (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
+            }
+        }
+        return new IntKeyframeSet(keyframes);
+    }
+
+    public static KeyframeSet ofFloat(float... values) {
+        int numKeyframes = values.length;
+        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
+        if (numKeyframes == 1) {
+            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
+            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
+        } else {
+            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
+            for (int i = 1; i < numKeyframes; ++i) {
+                keyframes[i] = (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
+            }
+        }
+        return new FloatKeyframeSet(keyframes);
+    }
+
+    public static KeyframeSet ofKeyframe(Keyframe... keyframes) {
+        // if all keyframes of same primitive type, create the appropriate KeyframeSet
+        int numKeyframes = keyframes.length;
+        boolean hasFloat = false;
+        boolean hasInt = false;
+        boolean hasOther = false;
+        for (int i = 0; i < numKeyframes; ++i) {
+            if (keyframes[i] instanceof FloatKeyframe) {
+                hasFloat = true;
+            } else if (keyframes[i] instanceof IntKeyframe) {
+                hasInt = true;
+            } else {
+                hasOther = true;
+            }
+        }
+        if (hasFloat && !hasInt && !hasOther) {
+            FloatKeyframe floatKeyframes[] = new FloatKeyframe[numKeyframes];
+            for (int i = 0; i < numKeyframes; ++i) {
+                floatKeyframes[i] = (FloatKeyframe) keyframes[i];
+            }
+            return new FloatKeyframeSet(floatKeyframes);
+        } else if (hasInt && !hasFloat && !hasOther) {
+            IntKeyframe intKeyframes[] = new IntKeyframe[numKeyframes];
+            for (int i = 0; i < numKeyframes; ++i) {
+                intKeyframes[i] = (IntKeyframe) keyframes[i];
+            }
+            return new IntKeyframeSet(intKeyframes);
+        } else {
+            return new KeyframeSet(keyframes);
+        }
+    }
+
+    public static KeyframeSet ofObject(Object... values) {
+        int numKeyframes = values.length;
+        ObjectKeyframe keyframes[] = new ObjectKeyframe[Math.max(numKeyframes,2)];
+        if (numKeyframes == 1) {
+            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f);
+            keyframes[1] = (ObjectKeyframe) Keyframe.ofObject(1f, values[0]);
+        } else {
+            keyframes[0] = (ObjectKeyframe) Keyframe.ofObject(0f, values[0]);
+            for (int i = 1; i < numKeyframes; ++i) {
+                keyframes[i] = (ObjectKeyframe) Keyframe.ofObject((float) i / (numKeyframes - 1), values[i]);
+            }
+        }
+        return new KeyframeSet(keyframes);
+    }
+
+    /**
+     * Sets the TypeEvaluator to be used when calculating animated values. This object
+     * is required only for KeyframeSets that are not either IntKeyframeSet or FloatKeyframeSet,
+     * both of which assume their own evaluator to speed up calculations with those primitive
+     * types.
+     *
+     * @param evaluator The TypeEvaluator to be used to calculate animated values.
+     */
+    public void setEvaluator(TypeEvaluator evaluator) {
+        mEvaluator = evaluator;
+    }
+
+    @Override
+    public KeyframeSet clone() {
+        ArrayList<Keyframe> keyframes = mKeyframes;
+        int numKeyframes = mKeyframes.size();
+        Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+        for (int i = 0; i < numKeyframes; ++i) {
+            newKeyframes[i] = keyframes.get(i).clone();
+        }
+        KeyframeSet newSet = new KeyframeSet(newKeyframes);
+        return newSet;
+    }
+
+    /**
+     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
+     * animation's interpolator) and the evaluator used to calculate in-between values. This
+     * function maps the input fraction to the appropriate keyframe interval and a fraction
+     * between them and returns the interpolated value. Note that the input fraction may fall
+     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
+     * spring interpolation that might send the fraction past 1.0). We handle this situation by
+     * just using the two keyframes at the appropriate end when the value is outside those bounds.
+     *
+     * @param fraction The elapsed fraction of the animation
+     * @return The animated value.
+     */
+    public Object getValue(float fraction) {
+
+        // Special-case optimization for the common case of only two keyframes
+        if (mNumKeyframes == 2) {
+            if (mInterpolator != null) {
+                fraction = mInterpolator.getInterpolation(fraction);
+            }
+            return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
+                    mLastKeyframe.getValue());
+        }
+        if (fraction <= 0f) {
+            final Keyframe nextKeyframe = mKeyframes.get(1);
+            final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            final float prevFraction = mFirstKeyframe.getFraction();
+            float intervalFraction = (fraction - prevFraction) /
+                (nextKeyframe.getFraction() - prevFraction);
+            return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
+                    nextKeyframe.getValue());
+        } else if (fraction >= 1f) {
+            final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
+            final /*Time*/Interpolator interpolator = mLastKeyframe.getInterpolator();
+            if (interpolator != null) {
+                fraction = interpolator.getInterpolation(fraction);
+            }
+            final float prevFraction = prevKeyframe.getFraction();
+            float intervalFraction = (fraction - prevFraction) /
+                (mLastKeyframe.getFraction() - prevFraction);
+            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                    mLastKeyframe.getValue());
+        }
+        Keyframe prevKeyframe = mFirstKeyframe;
+        for (int i = 1; i < mNumKeyframes; ++i) {
+            Keyframe nextKeyframe = mKeyframes.get(i);
+            if (fraction < nextKeyframe.getFraction()) {
+                final /*Time*/Interpolator interpolator = nextKeyframe.getInterpolator();
+                if (interpolator != null) {
+                    fraction = interpolator.getInterpolation(fraction);
+                }
+                final float prevFraction = prevKeyframe.getFraction();
+                float intervalFraction = (fraction - prevFraction) /
+                    (nextKeyframe.getFraction() - prevFraction);
+                return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
+                        nextKeyframe.getValue());
+            }
+            prevKeyframe = nextKeyframe;
+        }
+        // shouldn't reach here
+        return mLastKeyframe.getValue();
+    }
+
+    @Override
+    public String toString() {
+        String returnVal = " ";
+        for (int i = 0; i < mNumKeyframes; ++i) {
+            returnVal += mKeyframes.get(i).getValue() + "  ";
+        }
+        return returnVal;
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ObjectAnimator.java
@@ -0,0 +1,515 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.util.Log;
+import android.view.View;
+
+import com.nineoldandroids.util.Property;
+import com.nineoldandroids.view.animation.AnimatorProxy;
+
+/**
+ * This subclass of {@link ValueAnimator} provides support for animating properties on target objects.
+ * The constructors of this class take parameters to define the target object that will be animated
+ * as well as the name of the property that will be animated. Appropriate set/get functions
+ * are then determined internally and the animation will call these functions as necessary to
+ * animate the property.
+ *
+ * @see #setPropertyName(String)
+ *
+ */
+public final class ObjectAnimator extends ValueAnimator {
+    private static final boolean DBG = false;
+    private static final Map<String, Property> PROXY_PROPERTIES = new HashMap<String, Property>();
+
+    static {
+        PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
+        PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
+        PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
+        PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
+        PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
+        PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
+        PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
+        PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
+        PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
+        PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
+        PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
+        PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
+        PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
+        PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
+    }
+
+    // The target object on which the property exists, set in the constructor
+    private Object mTarget;
+
+    private String mPropertyName;
+
+    private Property mProperty;
+
+    /**
+     * Sets the name of the property that will be animated. This name is used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     *
+     * <p>For best performance of the mechanism that calls the setter function determined by the
+     * name of the property being animated, use <code>float</code> or <code>int</code> typed values,
+     * and make the setter function for those properties have a <code>void</code> return value. This
+     * will cause the code to take an optimized path for these constrained circumstances. Other
+     * property types and return types will work, but will have more overhead in processing
+     * the requests due to normal reflection mechanisms.</p>
+     *
+     * <p>Note that the setter function derived from this property name
+     * must take the same parameter type as the
+     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+     * the setter function will fail.</p>
+     *
+     * <p>If this ObjectAnimator has been set up to animate several properties together,
+     * using more than one PropertyValuesHolder objects, then setting the propertyName simply
+     * sets the propertyName in the first of those PropertyValuesHolder objects.</p>
+     *
+     * @param propertyName The name of the property being animated. Should not be null.
+     */
+    public void setPropertyName(String propertyName) {
+        // mValues could be null if this is being constructed piecemeal. Just record the
+        // propertyName to be used later when setValues() is called if so.
+        if (mValues != null) {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            String oldName = valuesHolder.getPropertyName();
+            valuesHolder.setPropertyName(propertyName);
+            mValuesMap.remove(oldName);
+            mValuesMap.put(propertyName, valuesHolder);
+        }
+        mPropertyName = propertyName;
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Sets the property that will be animated. Property objects will take precedence over
+     * properties specified by the {@link #setPropertyName(String)} method. Animations should
+     * be set up to use one or the other, not both.
+     *
+     * @param property The property being animated. Should not be null.
+     */
+    public void setProperty(Property property) {
+        // mValues could be null if this is being constructed piecemeal. Just record the
+        // propertyName to be used later when setValues() is called if so.
+        if (mValues != null) {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            String oldName = valuesHolder.getPropertyName();
+            valuesHolder.setProperty(property);
+            mValuesMap.remove(oldName);
+            mValuesMap.put(mPropertyName, valuesHolder);
+        }
+        if (mProperty != null) {
+            mPropertyName = property.getName();
+        }
+        mProperty = property;
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Gets the name of the property that will be animated. This name will be used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     */
+    public String getPropertyName() {
+        return mPropertyName;
+    }
+
+    /**
+     * Creates a new ObjectAnimator object. This default constructor is primarily for
+     * use internally; the other constructors which take parameters are more generally
+     * useful.
+     */
+    public ObjectAnimator() {
+    }
+
+    /**
+     * Private utility constructor that initializes the target object and name of the
+     * property being animated.
+     *
+     * @param target The object whose property is to be animated. This object should
+     * have a public method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     */
+    private ObjectAnimator(Object target, String propertyName) {
+        mTarget = target;
+        setPropertyName(propertyName);
+    }
+
+    /**
+     * Private utility constructor that initializes the target object and property being animated.
+     *
+     * @param target The object whose property is to be animated.
+     * @param property The property being animated.
+     */
+    private <T> ObjectAnimator(T target, Property<T, ?> property) {
+        mTarget = target;
+        setProperty(property);
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between int values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated. This object should
+     * have a public method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+        anim.setIntValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between int values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated.
+     * @param property The property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static <T> ObjectAnimator ofInt(T target, Property<T, Integer> property, int... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, property);
+        anim.setIntValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between float values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated. This object should
+     * have a public method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+        anim.setFloatValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between float values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated.
+     * @param property The property being animated.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
+            float... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, property);
+        anim.setFloatValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between Object values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated. This object should
+     * have a public method on it called <code>setName()</code>, where <code>name</code> is
+     * the value of the <code>propertyName</code> parameter.
+     * @param propertyName The name of the property being animated.
+     * @param evaluator A TypeEvaluator that will be called on each animation frame to
+     * provide the necessary interpolation between the Object values to derive the animated
+     * value.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static ObjectAnimator ofObject(Object target, String propertyName,
+            TypeEvaluator evaluator, Object... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
+        anim.setObjectValues(values);
+        anim.setEvaluator(evaluator);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between Object values. A single
+     * value implies that that value is the one being animated to. Two values imply a starting
+     * and ending values. More than two values imply a starting value, values to animate through
+     * along the way, and an ending value (these values will be distributed evenly across
+     * the duration of the animation).
+     *
+     * @param target The object whose property is to be animated.
+     * @param property The property being animated.
+     * @param evaluator A TypeEvaluator that will be called on each animation frame to
+     * provide the necessary interpolation between the Object values to derive the animated
+     * value.
+     * @param values A set of values that the animation will animate between over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static <T, V> ObjectAnimator ofObject(T target, Property<T, V> property,
+            TypeEvaluator<V> evaluator, V... values) {
+        ObjectAnimator anim = new ObjectAnimator(target, property);
+        anim.setObjectValues(values);
+        anim.setEvaluator(evaluator);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns an ObjectAnimator that animates between the sets of values specified
+     * in <code>PropertyValueHolder</code> objects. This variant should be used when animating
+     * several properties at once with the same ObjectAnimator, since PropertyValuesHolder allows
+     * you to associate a set of animation values with a property name.
+     *
+     * @param target The object whose property is to be animated. Depending on how the
+     * PropertyValuesObjects were constructed, the target object should either have the {@link
+     * android.util.Property} objects used to construct the PropertyValuesHolder objects or (if the
+     * PropertyValuesHOlder objects were created with property names) the target object should have
+     * public methods on it called <code>setName()</code>, where <code>name</code> is the name of
+     * the property passed in as the <code>propertyName</code> parameter for each of the
+     * PropertyValuesHolder objects.
+     * @param values A set of PropertyValuesHolder objects whose values will be animated between
+     * over time.
+     * @return An ObjectAnimator object that is set up to animate between the given values.
+     */
+    public static ObjectAnimator ofPropertyValuesHolder(Object target,
+            PropertyValuesHolder... values) {
+        ObjectAnimator anim = new ObjectAnimator();
+        anim.mTarget = target;
+        anim.setValues(values);
+        return anim;
+    }
+
+    @Override
+    public void setIntValues(int... values) {
+        if (mValues == null || mValues.length == 0) {
+            // No values yet - this animator is being constructed piecemeal. Init the values with
+            // whatever the current propertyName is
+            if (mProperty != null) {
+                setValues(PropertyValuesHolder.ofInt(mProperty, values));
+            } else {
+                setValues(PropertyValuesHolder.ofInt(mPropertyName, values));
+            }
+        } else {
+            super.setIntValues(values);
+        }
+    }
+
+    @Override
+    public void setFloatValues(float... values) {
+        if (mValues == null || mValues.length == 0) {
+            // No values yet - this animator is being constructed piecemeal. Init the values with
+            // whatever the current propertyName is
+            if (mProperty != null) {
+                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
+            } else {
+                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
+            }
+        } else {
+            super.setFloatValues(values);
+        }
+    }
+
+    @Override
+    public void setObjectValues(Object... values) {
+        if (mValues == null || mValues.length == 0) {
+            // No values yet - this animator is being constructed piecemeal. Init the values with
+            // whatever the current propertyName is
+            if (mProperty != null) {
+                setValues(PropertyValuesHolder.ofObject(mProperty, (TypeEvaluator)null, values));
+            } else {
+                setValues(PropertyValuesHolder.ofObject(mPropertyName, (TypeEvaluator)null, values));
+            }
+        } else {
+            super.setObjectValues(values);
+        }
+    }
+
+    @Override
+    public void start() {
+        if (DBG) {
+            Log.d("ObjectAnimator", "Anim target, duration: " + mTarget + ", " + getDuration());
+            for (int i = 0; i < mValues.length; ++i) {
+                PropertyValuesHolder pvh = mValues[i];
+                ArrayList<Keyframe> keyframes = pvh.mKeyframeSet.mKeyframes;
+                Log.d("ObjectAnimator", "   Values[" + i + "]: " +
+                    pvh.getPropertyName() + ", " + keyframes.get(0).getValue() + ", " +
+                    keyframes.get(pvh.mKeyframeSet.mNumKeyframes - 1).getValue());
+            }
+        }
+        super.start();
+    }
+
+    /**
+     * This function is called immediately before processing the first animation
+     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+     * function is called after that delay ends.
+     * It takes care of the final initialization steps for the
+     * animation. This includes setting mEvaluator, if the user has not yet
+     * set it up, and the setter/getter methods, if the user did not supply
+     * them.
+     *
+     *  <p>Overriders of this method should call the superclass method to cause
+     *  internal mechanisms to be set up correctly.</p>
+     */
+    @Override
+    void initAnimation() {
+        if (!mInitialized) {
+            // mValueType may change due to setter/getter setup; do this before calling super.init(),
+            // which uses mValueType to set up the default type evaluator.
+            if ((mProperty == null) && AnimatorProxy.NEEDS_PROXY && (mTarget instanceof View) && PROXY_PROPERTIES.containsKey(mPropertyName)) {
+                setProperty(PROXY_PROPERTIES.get(mPropertyName));
+            }
+            int numValues = mValues.length;
+            for (int i = 0; i < numValues; ++i) {
+                mValues[i].setupSetterAndGetter(mTarget);
+            }
+            super.initAnimation();
+        }
+    }
+
+    /**
+     * Sets the length of the animation. The default duration is 300 milliseconds.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @return ObjectAnimator The object called with setDuration(). This return
+     * value makes it easier to compose statements together that construct and then set the
+     * duration, as in
+     * <code>ObjectAnimator.ofInt(target, propertyName, 0, 10).setDuration(500).start()</code>.
+     */
+    @Override
+    public ObjectAnimator setDuration(long duration) {
+        super.setDuration(duration);
+        return this;
+    }
+
+
+    /**
+     * The target object whose property will be animated by this animation
+     *
+     * @return The object being animated
+     */
+    public Object getTarget() {
+        return mTarget;
+    }
+
+    /**
+     * Sets the target object whose property will be animated by this animation
+     *
+     * @param target The object being animated
+     */
+    @Override
+    public void setTarget(Object target) {
+        if (mTarget != target) {
+            final Object oldTarget = mTarget;
+            mTarget = target;
+            if (oldTarget != null && target != null && oldTarget.getClass() == target.getClass()) {
+                return;
+            }
+            // New target type should cause re-initialization prior to starting
+            mInitialized = false;
+        }
+    }
+
+    @Override
+    public void setupStartValues() {
+        initAnimation();
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setupStartValue(mTarget);
+        }
+    }
+
+    @Override
+    public void setupEndValues() {
+        initAnimation();
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setupEndValue(mTarget);
+        }
+    }
+
+    /**
+     * This method is called with the elapsed fraction of the animation during every
+     * animation frame. This function turns the elapsed fraction into an interpolated fraction
+     * and then into an animated value (from the evaluator. The function is called mostly during
+     * animation updates, but it is also called when the <code>end()</code>
+     * function is called, to set the final value on the property.
+     *
+     * <p>Overrides of this method must call the superclass to perform the calculation
+     * of the animated value.</p>
+     *
+     * @param fraction The elapsed fraction of the animation.
+     */
+    @Override
+    void animateValue(float fraction) {
+        super.animateValue(fraction);
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].setAnimatedValue(mTarget);
+        }
+    }
+
+    @Override
+    public ObjectAnimator clone() {
+        final ObjectAnimator anim = (ObjectAnimator) super.clone();
+        return anim;
+    }
+
+    @Override
+    public String toString() {
+        String returnVal = "ObjectAnimator@" + Integer.toHexString(hashCode()) + ", target " +
+            mTarget;
+        if (mValues != null) {
+            for (int i = 0; i < mValues.length; ++i) {
+                returnVal += "\n    " + mValues[i].toString();
+            }
+        }
+        return returnVal;
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/PreHoneycombCompat.java
@@ -0,0 +1,168 @@
+package com.nineoldandroids.animation;
+
+import android.view.View;
+import com.nineoldandroids.util.FloatProperty;
+import com.nineoldandroids.util.IntProperty;
+import com.nineoldandroids.util.Property;
+import com.nineoldandroids.view.animation.AnimatorProxy;
+
+final class PreHoneycombCompat {
+    static Property<View, Float> ALPHA = new FloatProperty<View>("alpha") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setAlpha(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getAlpha();
+        }
+    };
+    static Property<View, Float> PIVOT_X = new FloatProperty<View>("pivotX") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setPivotX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getPivotX();
+        }
+    };
+    static Property<View, Float> PIVOT_Y = new FloatProperty<View>("pivotY") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setPivotY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getPivotY();
+        }
+    };
+    static Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setTranslationX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getTranslationX();
+        }
+    };
+    static Property<View, Float> TRANSLATION_Y = new FloatProperty<View>("translationY") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setTranslationY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getTranslationY();
+        }
+    };
+    static Property<View, Float> ROTATION = new FloatProperty<View>("rotation") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setRotation(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getRotation();
+        }
+    };
+    static Property<View, Float> ROTATION_X = new FloatProperty<View>("rotationX") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setRotationX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getRotationX();
+        }
+    };
+    static Property<View, Float> ROTATION_Y = new FloatProperty<View>("rotationY") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setRotationY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getRotationY();
+        }
+    };
+    static Property<View, Float> SCALE_X = new FloatProperty<View>("scaleX") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setScaleX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getScaleX();
+        }
+    };
+    static Property<View, Float> SCALE_Y = new FloatProperty<View>("scaleY") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setScaleY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getScaleY();
+        }
+    };
+    static Property<View, Integer> SCROLL_X = new IntProperty<View>("scrollX") {
+        @Override
+        public void setValue(View object, int value) {
+            AnimatorProxy.wrap(object).setScrollX(value);
+        }
+
+        @Override
+        public Integer get(View object) {
+            return AnimatorProxy.wrap(object).getScrollX();
+        }
+    };
+    static Property<View, Integer> SCROLL_Y = new IntProperty<View>("scrollY") {
+        @Override
+        public void setValue(View object, int value) {
+            AnimatorProxy.wrap(object).setScrollY(value);
+        }
+
+        @Override
+        public Integer get(View object) {
+            return AnimatorProxy.wrap(object).getScrollY();
+        }
+    };
+    static Property<View, Float> X = new FloatProperty<View>("x") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setX(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getX();
+        }
+    };
+    static Property<View, Float> Y = new FloatProperty<View>("y") {
+        @Override
+        public void setValue(View object, float value) {
+            AnimatorProxy.wrap(object).setY(value);
+        }
+
+        @Override
+        public Float get(View object) {
+            return AnimatorProxy.wrap(object).getY();
+        }
+    };
+
+
+    //No instances
+    private PreHoneycombCompat() {}
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/PropertyValuesHolder.java
@@ -0,0 +1,1030 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import com.nineoldandroids.util.FloatProperty;
+import com.nineoldandroids.util.IntProperty;
+import com.nineoldandroids.util.Property;
+
+/**
+ * This class holds information about a property and the values that that property
+ * should take on during an animation. PropertyValuesHolder objects can be used to create
+ * animations with ValueAnimator or ObjectAnimator that operate on several different properties
+ * in parallel.
+ */
+public class PropertyValuesHolder implements Cloneable {
+
+    /**
+     * The name of the property associated with the values. This need not be a real property,
+     * unless this object is being used with ObjectAnimator. But this is the name by which
+     * aniamted values are looked up with getAnimatedValue(String) in ValueAnimator.
+     */
+    String mPropertyName;
+
+    /**
+     * @hide
+     */
+    protected Property mProperty;
+
+    /**
+     * The setter function, if needed. ObjectAnimator hands off this functionality to
+     * PropertyValuesHolder, since it holds all of the per-property information. This
+     * property is automatically
+     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+     */
+    Method mSetter = null;
+
+    /**
+     * The getter function, if needed. ObjectAnimator hands off this functionality to
+     * PropertyValuesHolder, since it holds all of the per-property information. This
+     * property is automatically
+     * derived when the animation starts in setupSetterAndGetter() if using ObjectAnimator.
+     * The getter is only derived and used if one of the values is null.
+     */
+    private Method mGetter = null;
+
+    /**
+     * The type of values supplied. This information is used both in deriving the setter/getter
+     * functions and in deriving the type of TypeEvaluator.
+     */
+    Class mValueType;
+
+    /**
+     * The set of keyframes (time/value pairs) that define this animation.
+     */
+    KeyframeSet mKeyframeSet = null;
+
+
+    // type evaluators for the primitive types handled by this implementation
+    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
+    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
+
+    // We try several different types when searching for appropriate setter/getter functions.
+    // The caller may have supplied values in a type that does not match the setter/getter
+    // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+    // Also, the use of generics in constructors means that we end up with the Object versions
+    // of primitive types (Float vs. float). But most likely, the setter/getter functions
+    // will take primitive types instead.
+    // So we supply an ordered array of other types to try before giving up.
+    private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+            Double.class, Integer.class};
+    private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+            Float.class, Double.class};
+    private static Class[] DOUBLE_VARIANTS = {double.class, Double.class, float.class, int.class,
+            Float.class, Integer.class};
+
+    // These maps hold all property entries for a particular class. This map
+    // is used to speed up property/setter/getter lookups for a given class/property
+    // combination. No need to use reflection on the combination more than once.
+    private static final HashMap<Class, HashMap<String, Method>> sSetterPropertyMap =
+            new HashMap<Class, HashMap<String, Method>>();
+    private static final HashMap<Class, HashMap<String, Method>> sGetterPropertyMap =
+            new HashMap<Class, HashMap<String, Method>>();
+
+    // This lock is used to ensure that only one thread is accessing the property maps
+    // at a time.
+    final ReentrantReadWriteLock mPropertyMapLock = new ReentrantReadWriteLock();
+
+    // Used to pass single value to varargs parameter in setter invocation
+    final Object[] mTmpValueArray = new Object[1];
+
+    /**
+     * The type evaluator used to calculate the animated values. This evaluator is determined
+     * automatically based on the type of the start/end objects passed into the constructor,
+     * but the system only knows about the primitive types int and float. Any other
+     * type will need to set the evaluator to a custom evaluator for that type.
+     */
+    private TypeEvaluator mEvaluator;
+
+    /**
+     * The value most recently calculated by calculateValue(). This is set during
+     * that function and might be retrieved later either by ValueAnimator.animatedValue() or
+     * by the property-setting logic in ObjectAnimator.animatedValue().
+     */
+    private Object mAnimatedValue;
+
+    /**
+     * Internal utility constructor, used by the factory methods to set the property name.
+     * @param propertyName The name of the property for this holder.
+     */
+    private PropertyValuesHolder(String propertyName) {
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * Internal utility constructor, used by the factory methods to set the property.
+     * @param property The property for this holder.
+     */
+    private PropertyValuesHolder(Property property) {
+        mProperty = property;
+        if (property != null) {
+            mPropertyName = property.getName();
+        }
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property name and
+     * set of int values.
+     * @param propertyName The name of the property being animated.
+     * @param values The values that the named property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static PropertyValuesHolder ofInt(String propertyName, int... values) {
+        return new IntPropertyValuesHolder(propertyName, values);
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property and
+     * set of int values.
+     * @param property The property being animated. Should not be null.
+     * @param values The values that the property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static PropertyValuesHolder ofInt(Property<?, Integer> property, int... values) {
+        return new IntPropertyValuesHolder(property, values);
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property name and
+     * set of float values.
+     * @param propertyName The name of the property being animated.
+     * @param values The values that the named property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
+        return new FloatPropertyValuesHolder(propertyName, values);
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property and
+     * set of float values.
+     * @param property The property being animated. Should not be null.
+     * @param values The values that the property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static PropertyValuesHolder ofFloat(Property<?, Float> property, float... values) {
+        return new FloatPropertyValuesHolder(property, values);
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property name and
+     * set of Object values. This variant also takes a TypeEvaluator because the system
+     * cannot automatically interpolate between objects of unknown type.
+     *
+     * @param propertyName The name of the property being animated.
+     * @param evaluator A TypeEvaluator that will be called on each animation frame to
+     * provide the necessary interpolation between the Object values to derive the animated
+     * value.
+     * @param values The values that the named property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,
+            Object... values) {
+        PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+        pvh.setObjectValues(values);
+        pvh.setEvaluator(evaluator);
+        return pvh;
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property and
+     * set of Object values. This variant also takes a TypeEvaluator because the system
+     * cannot automatically interpolate between objects of unknown type.
+     *
+     * @param property The property being animated. Should not be null.
+     * @param evaluator A TypeEvaluator that will be called on each animation frame to
+     * provide the necessary interpolation between the Object values to derive the animated
+     * value.
+     * @param values The values that the property will animate between.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    public static <V> PropertyValuesHolder ofObject(Property property,
+            TypeEvaluator<V> evaluator, V... values) {
+        PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+        pvh.setObjectValues(values);
+        pvh.setEvaluator(evaluator);
+        return pvh;
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder object with the specified property name and set
+     * of values. These values can be of any type, but the type should be consistent so that
+     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+     * the common type.
+     * <p>If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function
+     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     * @param propertyName The name of the property associated with this set of values. This
+     * can be the actual property name to be used when using a ObjectAnimator object, or
+     * just a name used to get animated values, such as if this object is used with an
+     * ValueAnimator object.
+     * @param values The set of values to animate between.
+     */
+    public static PropertyValuesHolder ofKeyframe(String propertyName, Keyframe... values) {
+        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+        if (keyframeSet instanceof IntKeyframeSet) {
+            return new IntPropertyValuesHolder(propertyName, (IntKeyframeSet) keyframeSet);
+        } else if (keyframeSet instanceof FloatKeyframeSet) {
+            return new FloatPropertyValuesHolder(propertyName, (FloatKeyframeSet) keyframeSet);
+        }
+        else {
+            PropertyValuesHolder pvh = new PropertyValuesHolder(propertyName);
+            pvh.mKeyframeSet = keyframeSet;
+            pvh.mValueType = ((Keyframe)values[0]).getType();
+            return pvh;
+        }
+    }
+
+    /**
+     * Constructs and returns a PropertyValuesHolder object with the specified property and set
+     * of values. These values can be of any type, but the type should be consistent so that
+     * an appropriate {@link android.animation.TypeEvaluator} can be found that matches
+     * the common type.
+     * <p>If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling the property's
+     * {@link android.util.Property#get(Object)} function.
+     * Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction with
+     * {@link ObjectAnimator}, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     * @param property The property associated with this set of values. Should not be null.
+     * @param values The set of values to animate between.
+     */
+    public static PropertyValuesHolder ofKeyframe(Property property, Keyframe... values) {
+        KeyframeSet keyframeSet = KeyframeSet.ofKeyframe(values);
+        if (keyframeSet instanceof IntKeyframeSet) {
+            return new IntPropertyValuesHolder(property, (IntKeyframeSet) keyframeSet);
+        } else if (keyframeSet instanceof FloatKeyframeSet) {
+            return new FloatPropertyValuesHolder(property, (FloatKeyframeSet) keyframeSet);
+        }
+        else {
+            PropertyValuesHolder pvh = new PropertyValuesHolder(property);
+            pvh.mKeyframeSet = keyframeSet;
+            pvh.mValueType = ((Keyframe)values[0]).getType();
+            return pvh;
+        }
+    }
+
+    /**
+     * Set the animated values for this object to this set of ints.
+     * If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function
+     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     *
+     * @param values One or more values that the animation will animate between.
+     */
+    public void setIntValues(int... values) {
+        mValueType = int.class;
+        mKeyframeSet = KeyframeSet.ofInt(values);
+    }
+
+    /**
+     * Set the animated values for this object to this set of floats.
+     * If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function
+     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     *
+     * @param values One or more values that the animation will animate between.
+     */
+    public void setFloatValues(float... values) {
+        mValueType = float.class;
+        mKeyframeSet = KeyframeSet.ofFloat(values);
+    }
+
+    /**
+     * Set the animated values for this object to this set of Keyframes.
+     *
+     * @param values One or more values that the animation will animate between.
+     */
+    public void setKeyframes(Keyframe... values) {
+        int numKeyframes = values.length;
+        Keyframe keyframes[] = new Keyframe[Math.max(numKeyframes,2)];
+        mValueType = ((Keyframe)values[0]).getType();
+        for (int i = 0; i < numKeyframes; ++i) {
+            keyframes[i] = (Keyframe)values[i];
+        }
+        mKeyframeSet = new KeyframeSet(keyframes);
+    }
+
+    /**
+     * Set the animated values for this object to this set of Objects.
+     * If there is only one value, it is assumed to be the end value of an animation,
+     * and an initial value will be derived, if possible, by calling a getter function
+     * on the object. Also, if any value is null, the value will be filled in when the animation
+     * starts in the same way. This mechanism of automatically getting null values only works
+     * if the PropertyValuesHolder object is used in conjunction
+     * {@link ObjectAnimator}, and with a getter function
+     * derived automatically from <code>propertyName</code>, since otherwise PropertyValuesHolder has
+     * no way of determining what the value should be.
+     *
+     * @param values One or more values that the animation will animate between.
+     */
+    public void setObjectValues(Object... values) {
+        mValueType = values[0].getClass();
+        mKeyframeSet = KeyframeSet.ofObject(values);
+    }
+
+    /**
+     * Determine the setter or getter function using the JavaBeans convention of setFoo or
+     * getFoo for a property named 'foo'. This function figures out what the name of the
+     * function should be and uses reflection to find the Method with that name on the
+     * target object.
+     *
+     * @param targetClass The class to search for the method
+     * @param prefix "set" or "get", depending on whether we need a setter or getter.
+     * @param valueType The type of the parameter (in the case of a setter). This type
+     * is derived from the values set on this PropertyValuesHolder. This type is used as
+     * a first guess at the parameter type, but we check for methods with several different
+     * types to avoid problems with slight mis-matches between supplied values and actual
+     * value types used on the setter.
+     * @return Method the method associated with mPropertyName.
+     */
+    private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
+        // TODO: faster implementation...
+        Method returnVal = null;
+        String methodName = getMethodName(prefix, mPropertyName);
+        Class args[] = null;
+        if (valueType == null) {
+            try {
+                returnVal = targetClass.getMethod(methodName, args);
+            } catch (NoSuchMethodException e) {
+                /* The native implementation uses JNI to do reflection, which allows access to private methods.
+                 * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
+                 */
+                try {
+                    returnVal = targetClass.getDeclaredMethod(methodName, args);
+                    returnVal.setAccessible(true);
+                } catch (NoSuchMethodException e2) {
+                    Log.e("PropertyValuesHolder",
+                            "Couldn't find no-arg method for property " + mPropertyName + ": " + e);
+                }
+            }
+        } else {
+            args = new Class[1];
+            Class typeVariants[];
+            if (mValueType.equals(Float.class)) {
+                typeVariants = FLOAT_VARIANTS;
+            } else if (mValueType.equals(Integer.class)) {
+                typeVariants = INTEGER_VARIANTS;
+            } else if (mValueType.equals(Double.class)) {
+                typeVariants = DOUBLE_VARIANTS;
+            } else {
+                typeVariants = new Class[1];
+                typeVariants[0] = mValueType;
+            }
+            for (Class typeVariant : typeVariants) {
+                args[0] = typeVariant;
+                try {
+                    returnVal = targetClass.getMethod(methodName, args);
+                    // change the value type to suit
+                    mValueType = typeVariant;
+                    return returnVal;
+                } catch (NoSuchMethodException e) {
+                    /* The native implementation uses JNI to do reflection, which allows access to private methods.
+                     * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
+                     */
+                    try {
+                        returnVal = targetClass.getDeclaredMethod(methodName, args);
+                        returnVal.setAccessible(true);
+                        // change the value type to suit
+                        mValueType = typeVariant;
+                        return returnVal;
+                    } catch (NoSuchMethodException e2) {
+                        // Swallow the error and keep trying other variants
+                    }
+                }
+            }
+            // If we got here, then no appropriate function was found
+            Log.e("PropertyValuesHolder",
+                    "Couldn't find setter/getter for property " + mPropertyName +
+                            " with value type "+ mValueType);
+        }
+
+        return returnVal;
+    }
+
+
+    /**
+     * Returns the setter or getter requested. This utility function checks whether the
+     * requested method exists in the propertyMapMap cache. If not, it calls another
+     * utility function to request the Method from the targetClass directly.
+     * @param targetClass The Class on which the requested method should exist.
+     * @param propertyMapMap The cache of setters/getters derived so far.
+     * @param prefix "set" or "get", for the setter or getter.
+     * @param valueType The type of parameter passed into the method (null for getter).
+     * @return Method the method associated with mPropertyName.
+     */
+    private Method setupSetterOrGetter(Class targetClass,
+            HashMap<Class, HashMap<String, Method>> propertyMapMap,
+            String prefix, Class valueType) {
+        Method setterOrGetter = null;
+        try {
+            // Have to lock property map prior to reading it, to guard against
+            // another thread putting something in there after we've checked it
+            // but before we've added an entry to it
+            mPropertyMapLock.writeLock().lock();
+            HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
+            if (propertyMap != null) {
+                setterOrGetter = propertyMap.get(mPropertyName);
+            }
+            if (setterOrGetter == null) {
+                setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
+                if (propertyMap == null) {
+                    propertyMap = new HashMap<String, Method>();
+                    propertyMapMap.put(targetClass, propertyMap);
+                }
+                propertyMap.put(mPropertyName, setterOrGetter);
+            }
+        } finally {
+            mPropertyMapLock.writeLock().unlock();
+        }
+        return setterOrGetter;
+    }
+
+    /**
+     * Utility function to get the setter from targetClass
+     * @param targetClass The Class on which the requested method should exist.
+     */
+    void setupSetter(Class targetClass) {
+        mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", mValueType);
+    }
+
+    /**
+     * Utility function to get the getter from targetClass
+     */
+    private void setupGetter(Class targetClass) {
+        mGetter = setupSetterOrGetter(targetClass, sGetterPropertyMap, "get", null);
+    }
+
+    /**
+     * Internal function (called from ObjectAnimator) to set up the setter and getter
+     * prior to running the animation. If the setter has not been manually set for this
+     * object, it will be derived automatically given the property name, target object, and
+     * types of values supplied. If no getter has been set, it will be supplied iff any of the
+     * supplied values was null. If there is a null value, then the getter (supplied or derived)
+     * will be called to set those null values to the current value of the property
+     * on the target object.
+     * @param target The object on which the setter (and possibly getter) exist.
+     */
+    void setupSetterAndGetter(Object target) {
+        if (mProperty != null) {
+            // check to make sure that mProperty is on the class of target
+            try {
+                Object testValue = mProperty.get(target);
+                for (Keyframe kf : mKeyframeSet.mKeyframes) {
+                    if (!kf.hasValue()) {
+                        kf.setValue(mProperty.get(target));
+                    }
+                }
+                return;
+            } catch (ClassCastException e) {
+                Log.e("PropertyValuesHolder","No such property (" + mProperty.getName() +
+                        ") on target object " + target + ". Trying reflection instead");
+                mProperty = null;
+            }
+        }
+        Class targetClass = target.getClass();
+        if (mSetter == null) {
+            setupSetter(targetClass);
+        }
+        for (Keyframe kf : mKeyframeSet.mKeyframes) {
+            if (!kf.hasValue()) {
+                if (mGetter == null) {
+                    setupGetter(targetClass);
+                }
+                try {
+                    kf.setValue(mGetter.invoke(target));
+                } catch (InvocationTargetException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                } catch (IllegalAccessException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * Utility function to set the value stored in a particular Keyframe. The value used is
+     * whatever the value is for the property name specified in the keyframe on the target object.
+     *
+     * @param target The target object from which the current value should be extracted.
+     * @param kf The keyframe which holds the property name and value.
+     */
+    private void setupValue(Object target, Keyframe kf) {
+        if (mProperty != null) {
+            kf.setValue(mProperty.get(target));
+        }
+        try {
+            if (mGetter == null) {
+                Class targetClass = target.getClass();
+                setupGetter(targetClass);
+            }
+            kf.setValue(mGetter.invoke(target));
+        } catch (InvocationTargetException e) {
+            Log.e("PropertyValuesHolder", e.toString());
+        } catch (IllegalAccessException e) {
+            Log.e("PropertyValuesHolder", e.toString());
+        }
+    }
+
+    /**
+     * This function is called by ObjectAnimator when setting the start values for an animation.
+     * The start values are set according to the current values in the target object. The
+     * property whose value is extracted is whatever is specified by the propertyName of this
+     * PropertyValuesHolder object.
+     *
+     * @param target The object which holds the start values that should be set.
+     */
+    void setupStartValue(Object target) {
+        setupValue(target, mKeyframeSet.mKeyframes.get(0));
+    }
+
+    /**
+     * This function is called by ObjectAnimator when setting the end values for an animation.
+     * The end values are set according to the current values in the target object. The
+     * property whose value is extracted is whatever is specified by the propertyName of this
+     * PropertyValuesHolder object.
+     *
+     * @param target The object which holds the start values that should be set.
+     */
+    void setupEndValue(Object target) {
+        setupValue(target, mKeyframeSet.mKeyframes.get(mKeyframeSet.mKeyframes.size() - 1));
+    }
+
+    @Override
+    public PropertyValuesHolder clone() {
+        try {
+            PropertyValuesHolder newPVH = (PropertyValuesHolder) super.clone();
+            newPVH.mPropertyName = mPropertyName;
+            newPVH.mProperty = mProperty;
+            newPVH.mKeyframeSet = mKeyframeSet.clone();
+            newPVH.mEvaluator = mEvaluator;
+            return newPVH;
+        } catch (CloneNotSupportedException e) {
+            // won't reach here
+            return null;
+        }
+    }
+
+    /**
+     * Internal function to set the value on the target object, using the setter set up
+     * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+     * to handle turning the value calculated by ValueAnimator into a value set on the object
+     * according to the name of the property.
+     * @param target The target object on which the value is set
+     */
+    void setAnimatedValue(Object target) {
+        if (mProperty != null) {
+            mProperty.set(target, getAnimatedValue());
+        }
+        if (mSetter != null) {
+            try {
+                mTmpValueArray[0] = getAnimatedValue();
+                mSetter.invoke(target, mTmpValueArray);
+            } catch (InvocationTargetException e) {
+                Log.e("PropertyValuesHolder", e.toString());
+            } catch (IllegalAccessException e) {
+                Log.e("PropertyValuesHolder", e.toString());
+            }
+        }
+    }
+
+    /**
+     * Internal function, called by ValueAnimator, to set up the TypeEvaluator that will be used
+     * to calculate animated values.
+     */
+    void init() {
+        if (mEvaluator == null) {
+            // We already handle int and float automatically, but not their Object
+            // equivalents
+            mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
+                    (mValueType == Float.class) ? sFloatEvaluator :
+                    null;
+        }
+        if (mEvaluator != null) {
+            // KeyframeSet knows how to evaluate the common types - only give it a custom
+            // evaluator if one has been set on this class
+            mKeyframeSet.setEvaluator(mEvaluator);
+        }
+    }
+
+    /**
+     * The TypeEvaluator will the automatically determined based on the type of values
+     * supplied to PropertyValuesHolder. The evaluator can be manually set, however, if so
+     * desired. This may be important in cases where either the type of the values supplied
+     * do not match the way that they should be interpolated between, or if the values
+     * are of a custom type or one not currently understood by the animation system. Currently,
+     * only values of type float and int (and their Object equivalents: Float
+     * and Integer) are  correctly interpolated; all other types require setting a TypeEvaluator.
+     * @param evaluator
+     */
+    public void setEvaluator(TypeEvaluator evaluator) {
+        mEvaluator = evaluator;
+        mKeyframeSet.setEvaluator(evaluator);
+    }
+
+    /**
+     * Function used to calculate the value according to the evaluator set up for
+     * this PropertyValuesHolder object. This function is called by ValueAnimator.animateValue().
+     *
+     * @param fraction The elapsed, interpolated fraction of the animation.
+     */
+    void calculateValue(float fraction) {
+        mAnimatedValue = mKeyframeSet.getValue(fraction);
+    }
+
+    /**
+     * Sets the name of the property that will be animated. This name is used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     *
+     * <p>Note that the setter function derived from this property name
+     * must take the same parameter type as the
+     * <code>valueFrom</code> and <code>valueTo</code> properties, otherwise the call to
+     * the setter function will fail.</p>
+     *
+     * @param propertyName The name of the property being animated.
+     */
+    public void setPropertyName(String propertyName) {
+        mPropertyName = propertyName;
+    }
+
+    /**
+     * Sets the property that will be animated.
+     *
+     * <p>Note that if this PropertyValuesHolder object is used with ObjectAnimator, the property
+     * must exist on the target object specified in that ObjectAnimator.</p>
+     *
+     * @param property The property being animated.
+     */
+    public void setProperty(Property property) {
+        mProperty = property;
+    }
+
+    /**
+     * Gets the name of the property that will be animated. This name will be used to derive
+     * a setter function that will be called to set animated values.
+     * For example, a property name of <code>foo</code> will result
+     * in a call to the function <code>setFoo()</code> on the target object. If either
+     * <code>valueFrom</code> or <code>valueTo</code> is null, then a getter function will
+     * also be derived and called.
+     */
+    public String getPropertyName() {
+        return mPropertyName;
+    }
+
+    /**
+     * Internal function, called by ValueAnimator and ObjectAnimator, to retrieve the value
+     * most recently calculated in calculateValue().
+     * @return
+     */
+    Object getAnimatedValue() {
+        return mAnimatedValue;
+    }
+
+    @Override
+    public String toString() {
+        return mPropertyName + ": " + mKeyframeSet.toString();
+    }
+
+    /**
+     * Utility method to derive a setter/getter method name from a property name, where the
+     * prefix is typically "set" or "get" and the first letter of the property name is
+     * capitalized.
+     *
+     * @param prefix The precursor to the method name, before the property name begins, typically
+     * "set" or "get".
+     * @param propertyName The name of the property that represents the bulk of the method name
+     * after the prefix. The first letter of this word will be capitalized in the resulting
+     * method name.
+     * @return String the property name converted to a method name according to the conventions
+     * specified above.
+     */
+    static String getMethodName(String prefix, String propertyName) {
+        if (propertyName == null || propertyName.length() == 0) {
+            // shouldn't get here
+            return prefix;
+        }
+        char firstLetter = Character.toUpperCase(propertyName.charAt(0));
+        String theRest = propertyName.substring(1);
+        return prefix + firstLetter + theRest;
+    }
+
+    static class IntPropertyValuesHolder extends PropertyValuesHolder {
+
+        // Cache JNI functions to avoid looking them up twice
+        //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
+        //        new HashMap<Class, HashMap<String, Integer>>();
+        //int mJniSetter;
+        private IntProperty mIntProperty;
+
+        IntKeyframeSet mIntKeyframeSet;
+        int mIntAnimatedValue;
+
+        public IntPropertyValuesHolder(String propertyName, IntKeyframeSet keyframeSet) {
+            super(propertyName);
+            mValueType = int.class;
+            mKeyframeSet = keyframeSet;
+            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+        }
+
+        public IntPropertyValuesHolder(Property property, IntKeyframeSet keyframeSet) {
+            super(property);
+            mValueType = int.class;
+            mKeyframeSet = keyframeSet;
+            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+            if (property instanceof  IntProperty) {
+                mIntProperty = (IntProperty) mProperty;
+            }
+        }
+
+        public IntPropertyValuesHolder(String propertyName, int... values) {
+            super(propertyName);
+            setIntValues(values);
+        }
+
+        public IntPropertyValuesHolder(Property property, int... values) {
+            super(property);
+            setIntValues(values);
+            if (property instanceof  IntProperty) {
+                mIntProperty = (IntProperty) mProperty;
+            }
+        }
+
+        @Override
+        public void setIntValues(int... values) {
+            super.setIntValues(values);
+            mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet;
+        }
+
+        @Override
+        void calculateValue(float fraction) {
+            mIntAnimatedValue = mIntKeyframeSet.getIntValue(fraction);
+        }
+
+        @Override
+        Object getAnimatedValue() {
+            return mIntAnimatedValue;
+        }
+
+        @Override
+        public IntPropertyValuesHolder clone() {
+            IntPropertyValuesHolder newPVH = (IntPropertyValuesHolder) super.clone();
+            newPVH.mIntKeyframeSet = (IntKeyframeSet) newPVH.mKeyframeSet;
+            return newPVH;
+        }
+
+        /**
+         * Internal function to set the value on the target object, using the setter set up
+         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+         * to handle turning the value calculated by ValueAnimator into a value set on the object
+         * according to the name of the property.
+         * @param target The target object on which the value is set
+         */
+        @Override
+        void setAnimatedValue(Object target) {
+            if (mIntProperty != null) {
+                mIntProperty.setValue(target, mIntAnimatedValue);
+                return;
+            }
+            if (mProperty != null) {
+                mProperty.set(target, mIntAnimatedValue);
+                return;
+            }
+            //if (mJniSetter != 0) {
+            //    nCallIntMethod(target, mJniSetter, mIntAnimatedValue);
+            //    return;
+            //}
+            if (mSetter != null) {
+                try {
+                    mTmpValueArray[0] = mIntAnimatedValue;
+                    mSetter.invoke(target, mTmpValueArray);
+                } catch (InvocationTargetException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                } catch (IllegalAccessException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                }
+            }
+        }
+
+        @Override
+        void setupSetter(Class targetClass) {
+            if (mProperty != null) {
+                return;
+            }
+            // Check new static hashmap<propName, int> for setter method
+            //try {
+            //    mPropertyMapLock.writeLock().lock();
+            //    HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
+            //    if (propertyMap != null) {
+            //        Integer mJniSetterInteger = propertyMap.get(mPropertyName);
+            //        if (mJniSetterInteger != null) {
+            //            mJniSetter = mJniSetterInteger;
+            //        }
+            //    }
+            //    if (mJniSetter == 0) {
+            //        String methodName = getMethodName("set", mPropertyName);
+            //        mJniSetter = nGetIntMethod(targetClass, methodName);
+            //        if (mJniSetter != 0) {
+            //            if (propertyMap == null) {
+            //                propertyMap = new HashMap<String, Integer>();
+            //                sJNISetterPropertyMap.put(targetClass, propertyMap);
+            //            }
+            //            propertyMap.put(mPropertyName, mJniSetter);
+            //        }
+            //    }
+            //} catch (NoSuchMethodError e) {
+            //    Log.d("PropertyValuesHolder",
+            //            "Can't find native method using JNI, use reflection" + e);
+            //} finally {
+            //    mPropertyMapLock.writeLock().unlock();
+            //}
+            //if (mJniSetter == 0) {
+                // Couldn't find method through fast JNI approach - just use reflection
+                super.setupSetter(targetClass);
+            //}
+        }
+    }
+
+    static class FloatPropertyValuesHolder extends PropertyValuesHolder {
+
+        // Cache JNI functions to avoid looking them up twice
+        //private static final HashMap<Class, HashMap<String, Integer>> sJNISetterPropertyMap =
+        //        new HashMap<Class, HashMap<String, Integer>>();
+        //int mJniSetter;
+        private FloatProperty mFloatProperty;
+
+        FloatKeyframeSet mFloatKeyframeSet;
+        float mFloatAnimatedValue;
+
+        public FloatPropertyValuesHolder(String propertyName, FloatKeyframeSet keyframeSet) {
+            super(propertyName);
+            mValueType = float.class;
+            mKeyframeSet = keyframeSet;
+            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+        }
+
+        public FloatPropertyValuesHolder(Property property, FloatKeyframeSet keyframeSet) {
+            super(property);
+            mValueType = float.class;
+            mKeyframeSet = keyframeSet;
+            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+            if (property instanceof FloatProperty) {
+                mFloatProperty = (FloatProperty) mProperty;
+            }
+        }
+
+        public FloatPropertyValuesHolder(String propertyName, float... values) {
+            super(propertyName);
+            setFloatValues(values);
+        }
+
+        public FloatPropertyValuesHolder(Property property, float... values) {
+            super(property);
+            setFloatValues(values);
+            if (property instanceof  FloatProperty) {
+                mFloatProperty = (FloatProperty) mProperty;
+            }
+        }
+
+        @Override
+        public void setFloatValues(float... values) {
+            super.setFloatValues(values);
+            mFloatKeyframeSet = (FloatKeyframeSet) mKeyframeSet;
+        }
+
+        @Override
+        void calculateValue(float fraction) {
+            mFloatAnimatedValue = mFloatKeyframeSet.getFloatValue(fraction);
+        }
+
+        @Override
+        Object getAnimatedValue() {
+            return mFloatAnimatedValue;
+        }
+
+        @Override
+        public FloatPropertyValuesHolder clone() {
+            FloatPropertyValuesHolder newPVH = (FloatPropertyValuesHolder) super.clone();
+            newPVH.mFloatKeyframeSet = (FloatKeyframeSet) newPVH.mKeyframeSet;
+            return newPVH;
+        }
+
+        /**
+         * Internal function to set the value on the target object, using the setter set up
+         * earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
+         * to handle turning the value calculated by ValueAnimator into a value set on the object
+         * according to the name of the property.
+         * @param target The target object on which the value is set
+         */
+        @Override
+        void setAnimatedValue(Object target) {
+            if (mFloatProperty != null) {
+                mFloatProperty.setValue(target, mFloatAnimatedValue);
+                return;
+            }
+            if (mProperty != null) {
+                mProperty.set(target, mFloatAnimatedValue);
+                return;
+            }
+            //if (mJniSetter != 0) {
+            //    nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
+            //    return;
+            //}
+            if (mSetter != null) {
+                try {
+                    mTmpValueArray[0] = mFloatAnimatedValue;
+                    mSetter.invoke(target, mTmpValueArray);
+                } catch (InvocationTargetException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                } catch (IllegalAccessException e) {
+                    Log.e("PropertyValuesHolder", e.toString());
+                }
+            }
+        }
+
+        @Override
+        void setupSetter(Class targetClass) {
+            if (mProperty != null) {
+                return;
+            }
+            // Check new static hashmap<propName, int> for setter method
+            //try {
+            //    mPropertyMapLock.writeLock().lock();
+            //    HashMap<String, Integer> propertyMap = sJNISetterPropertyMap.get(targetClass);
+            //    if (propertyMap != null) {
+            //        Integer mJniSetterInteger = propertyMap.get(mPropertyName);
+            //        if (mJniSetterInteger != null) {
+            //            mJniSetter = mJniSetterInteger;
+            //        }
+            //    }
+            //    if (mJniSetter == 0) {
+            //        String methodName = getMethodName("set", mPropertyName);
+            //        mJniSetter = nGetFloatMethod(targetClass, methodName);
+            //        if (mJniSetter != 0) {
+            //            if (propertyMap == null) {
+            //                propertyMap = new HashMap<String, Integer>();
+            //                sJNISetterPropertyMap.put(targetClass, propertyMap);
+            //            }
+            //            propertyMap.put(mPropertyName, mJniSetter);
+            //        }
+            //    }
+            //} catch (NoSuchMethodError e) {
+            //    Log.d("PropertyValuesHolder",
+            //            "Can't find native method using JNI, use reflection" + e);
+            //} finally {
+            //    mPropertyMapLock.writeLock().unlock();
+            //}
+            //if (mJniSetter == 0) {
+                // Couldn't find method through fast JNI approach - just use reflection
+                super.setupSetter(targetClass);
+            //}
+        }
+
+    }
+
+    //native static private int nGetIntMethod(Class targetClass, String methodName);
+    //native static private int nGetFloatMethod(Class targetClass, String methodName);
+    //native static private void nCallIntMethod(Object target, int methodID, int arg);
+    //native static private void nCallFloatMethod(Object target, int methodID, float arg);
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/TimeAnimator.java
@@ -0,0 +1,78 @@
+package com.nineoldandroids.animation;
+
+/**
+ * This class provides a simple callback mechanism to listeners that is synchronized with other
+ * animators in the system. There is no duration, interpolation, or object value-setting
+ * with this Animator. Instead, it is simply started and proceeds to send out events on every
+ * animation frame to its TimeListener (if set), with information about this animator,
+ * the total elapsed time, and the time since the last animation frame.
+ *
+ * @hide
+ */
+public class TimeAnimator extends ValueAnimator {
+
+    private TimeListener mListener;
+    private long mPreviousTime = -1;
+
+    @Override
+    boolean animationFrame(long currentTime) {
+        if (mPlayingState == STOPPED) {
+            mPlayingState = RUNNING;
+            if (mSeekTime < 0) {
+                mStartTime = currentTime;
+            } else {
+                mStartTime = currentTime - mSeekTime;
+                // Now that we're playing, reset the seek time
+                mSeekTime = -1;
+            }
+        }
+        if (mListener != null) {
+            long totalTime = currentTime - mStartTime;
+            long deltaTime = (mPreviousTime < 0) ? 0 : (currentTime - mPreviousTime);
+            mPreviousTime = currentTime;
+            mListener.onTimeUpdate(this, totalTime, deltaTime);
+        }
+        return false;
+    }
+
+    /**
+     * Sets a listener that is sent update events throughout the life of
+     * an animation.
+     *
+     * @param listener the listener to be set.
+     */
+    public void setTimeListener(TimeListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    void animateValue(float fraction) {
+        // Noop
+    }
+
+    @Override
+    void initAnimation() {
+        // noop
+    }
+
+    /**
+     * Implementors of this interface can set themselves as update listeners
+     * to a <code>TimeAnimator</code> instance to receive callbacks on every animation
+     * frame to receive the total time since the animator started and the delta time
+     * since the last frame. The first time the listener is called, totalTime and
+     * deltaTime should both be zero.
+     *
+     * @hide
+     */
+    public static interface TimeListener {
+        /**
+         * <p>Notifies listeners of the occurrence of another frame of the animation,
+         * along with information about the elapsed time.</p>
+         *
+         * @param animation The animator sending out the notification.
+         * @param totalTime The
+         */
+        void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime);
+
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/TypeEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+/**
+ * Interface for use with the {@link ValueAnimator#setEvaluator(TypeEvaluator)} function. Evaluators
+ * allow developers to create animations on arbitrary property types, by allowing them to supply
+ * custom evaulators for types that are not automatically understood and used by the animation
+ * system.
+ *
+ * @see ValueAnimator#setEvaluator(TypeEvaluator)
+ */
+public interface TypeEvaluator<T> {
+
+    /**
+     * This function returns the result of linearly interpolating the start and end values, with
+     * <code>fraction</code> representing the proportion between the start and end values. The
+     * calculation is a simple parametric calculation: <code>result = x0 + t * (v1 - v0)</code>,
+     * where <code>x0</code> is <code>startValue</code>, <code>x1</code> is <code>endValue</code>,
+     * and <code>t</code> is <code>fraction</code>.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value.
+     * @param endValue   The end value.
+     * @return A linear interpolation between the start and end values, given the
+     *         <code>fraction</code> parameter.
+     */
+    public T evaluate(float fraction, T startValue, T endValue);
+
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/animation/ValueAnimator.java
@@ -0,0 +1,1264 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.animation;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AndroidRuntimeException;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.view.animation.LinearInterpolator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * This class provides a simple timing engine for running animations
+ * which calculate animated values and set them on target objects.
+ *
+ * <p>There is a single timing pulse that all animations use. It runs in a
+ * custom handler to ensure that property changes happen on the UI thread.</p>
+ *
+ * <p>By default, ValueAnimator uses non-linear time interpolation, via the
+ * {@link AccelerateDecelerateInterpolator} class, which accelerates into and decelerates
+ * out of an animation. This behavior can be changed by calling
+ * {@link ValueAnimator#setInterpolator(TimeInterpolator)}.</p>
+ */
+public class ValueAnimator extends Animator {
+
+    /**
+     * Internal constants
+     */
+
+    /*
+     * The default amount of time in ms between animation frames
+     */
+    private static final long DEFAULT_FRAME_DELAY = 10;
+
+    /**
+     * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent
+     * by the handler to itself to process the next animation frame
+     */
+    static final int ANIMATION_START = 0;
+    static final int ANIMATION_FRAME = 1;
+
+    /**
+     * Values used with internal variable mPlayingState to indicate the current state of an
+     * animation.
+     */
+    static final int STOPPED    = 0; // Not yet playing
+    static final int RUNNING    = 1; // Playing normally
+    static final int SEEKED     = 2; // Seeked to some time value
+
+    /**
+     * Internal variables
+     * NOTE: This object implements the clone() method, making a deep copy of any referenced
+     * objects. As other non-trivial fields are added to this class, make sure to add logic
+     * to clone() to make deep copies of them.
+     */
+
+    // The first time that the animation's animateFrame() method is called. This time is used to
+    // determine elapsed time (and therefore the elapsed fraction) in subsequent calls
+    // to animateFrame()
+    long mStartTime;
+
+    /**
+     * Set when setCurrentPlayTime() is called. If negative, animation is not currently seeked
+     * to a value.
+     */
+    long mSeekTime = -1;
+
+    // TODO: We access the following ThreadLocal variables often, some of them on every update.
+    // If ThreadLocal access is significantly expensive, we may want to put all of these
+    // fields into a structure sot hat we just access ThreadLocal once to get the reference
+    // to that structure, then access the structure directly for each field.
+
+    // The static sAnimationHandler processes the internal timing loop on which all animations
+    // are based
+    private static ThreadLocal<AnimationHandler> sAnimationHandler =
+            new ThreadLocal<AnimationHandler>();
+
+    // The per-thread list of all active animations
+    private static final ThreadLocal<ArrayList<ValueAnimator>> sAnimations =
+            new ThreadLocal<ArrayList<ValueAnimator>>() {
+                @Override
+                protected ArrayList<ValueAnimator> initialValue() {
+                    return new ArrayList<ValueAnimator>();
+                }
+            };
+
+    // The per-thread set of animations to be started on the next animation frame
+    private static final ThreadLocal<ArrayList<ValueAnimator>> sPendingAnimations =
+            new ThreadLocal<ArrayList<ValueAnimator>>() {
+                @Override
+                protected ArrayList<ValueAnimator> initialValue() {
+                    return new ArrayList<ValueAnimator>();
+                }
+            };
+
+    /**
+     * Internal per-thread collections used to avoid set collisions as animations start and end
+     * while being processed.
+     */
+    private static final ThreadLocal<ArrayList<ValueAnimator>> sDelayedAnims =
+            new ThreadLocal<ArrayList<ValueAnimator>>() {
+                @Override
+                protected ArrayList<ValueAnimator> initialValue() {
+                    return new ArrayList<ValueAnimator>();
+                }
+            };
+
+    private static final ThreadLocal<ArrayList<ValueAnimator>> sEndingAnims =
+            new ThreadLocal<ArrayList<ValueAnimator>>() {
+                @Override
+                protected ArrayList<ValueAnimator> initialValue() {
+                    return new ArrayList<ValueAnimator>();
+                }
+            };
+
+    private static final ThreadLocal<ArrayList<ValueAnimator>> sReadyAnims =
+            new ThreadLocal<ArrayList<ValueAnimator>>() {
+                @Override
+                protected ArrayList<ValueAnimator> initialValue() {
+                    return new ArrayList<ValueAnimator>();
+                }
+            };
+
+    // The time interpolator to be used if none is set on the animation
+    private static final /*Time*/Interpolator sDefaultInterpolator =
+            new AccelerateDecelerateInterpolator();
+
+    // type evaluators for the primitive types handled by this implementation
+    private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
+    private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
+
+    /**
+     * Used to indicate whether the animation is currently playing in reverse. This causes the
+     * elapsed fraction to be inverted to calculate the appropriate values.
+     */
+    private boolean mPlayingBackwards = false;
+
+    /**
+     * This variable tracks the current iteration that is playing. When mCurrentIteration exceeds the
+     * repeatCount (if repeatCount!=INFINITE), the animation ends
+     */
+    private int mCurrentIteration = 0;
+
+    /**
+     * Tracks current elapsed/eased fraction, for querying in getAnimatedFraction().
+     */
+    private float mCurrentFraction = 0f;
+
+    /**
+     * Tracks whether a startDelay'd animation has begun playing through the startDelay.
+     */
+    private boolean mStartedDelay = false;
+
+    /**
+     * Tracks the time at which the animation began playing through its startDelay. This is
+     * different from the mStartTime variable, which is used to track when the animation became
+     * active (which is when the startDelay expired and the animation was added to the active
+     * animations list).
+     */
+    private long mDelayStartTime;
+
+    /**
+     * Flag that represents the current state of the animation. Used to figure out when to start
+     * an animation (if state == STOPPED). Also used to end an animation that
+     * has been cancel()'d or end()'d since the last animation frame. Possible values are
+     * STOPPED, RUNNING, SEEKED.
+     */
+    int mPlayingState = STOPPED;
+
+    /**
+     * Additional playing state to indicate whether an animator has been start()'d. There is
+     * some lag between a call to start() and the first animation frame. We should still note
+     * that the animation has been started, even if it's first animation frame has not yet
+     * happened, and reflect that state in isRunning().
+     * Note that delayed animations are different: they are not started until their first
+     * animation frame, which occurs after their delay elapses.
+     */
+    private boolean mRunning = false;
+
+    /**
+     * Additional playing state to indicate whether an animator has been start()'d, whether or
+     * not there is a nonzero startDelay.
+     */
+    private boolean mStarted = false;
+
+    /**
+     * Flag that denotes whether the animation is set up and ready to go. Used to
+     * set up animation that has not yet been started.
+     */
+    boolean mInitialized = false;
+
+    //
+    // Backing variables
+    //
+
+    // How long the animation should last in ms
+    private long mDuration = 300;
+
+    // The amount of time in ms to delay starting the animation after start() is called
+    private long mStartDelay = 0;
+
+    // The number of milliseconds between animation frames
+    private static long sFrameDelay = DEFAULT_FRAME_DELAY;
+
+    // The number of times the animation will repeat. The default is 0, which means the animation
+    // will play only once
+    private int mRepeatCount = 0;
+
+    /**
+     * The type of repetition that will occur when repeatMode is nonzero. RESTART means the
+     * animation will start from the beginning on every new cycle. REVERSE means the animation
+     * will reverse directions on each iteration.
+     */
+    private int mRepeatMode = RESTART;
+
+    /**
+     * The time interpolator to be used. The elapsed fraction of the animation will be passed
+     * through this interpolator to calculate the interpolated fraction, which is then used to
+     * calculate the animated values.
+     */
+    private /*Time*/Interpolator mInterpolator = sDefaultInterpolator;
+
+    /**
+     * The set of listeners to be sent events through the life of an animation.
+     */
+    private ArrayList<AnimatorUpdateListener> mUpdateListeners = null;
+
+    /**
+     * The property/value sets being animated.
+     */
+    PropertyValuesHolder[] mValues;
+
+    /**
+     * A hashmap of the PropertyValuesHolder objects. This map is used to lookup animated values
+     * by property name during calls to getAnimatedValue(String).
+     */
+    HashMap<String, PropertyValuesHolder> mValuesMap;
+
+    /**
+     * Public constants
+     */
+
+    /**
+     * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+     * or a positive value, the animation restarts from the beginning.
+     */
+    public static final int RESTART = 1;
+    /**
+     * When the animation reaches the end and <code>repeatCount</code> is INFINITE
+     * or a positive value, the animation reverses direction on every iteration.
+     */
+    public static final int REVERSE = 2;
+    /**
+     * This value used used with the {@link #setRepeatCount(int)} property to repeat
+     * the animation indefinitely.
+     */
+    public static final int INFINITE = -1;
+
+    /**
+     * Creates a new ValueAnimator object. This default constructor is primarily for
+     * use internally; the factory methods which take parameters are more generally
+     * useful.
+     */
+    public ValueAnimator() {
+    }
+
+    /**
+     * Constructs and returns a ValueAnimator that animates between int values. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * @param values A set of values that the animation will animate between over time.
+     * @return A ValueAnimator object that is set up to animate between the given values.
+     */
+    public static ValueAnimator ofInt(int... values) {
+        ValueAnimator anim = new ValueAnimator();
+        anim.setIntValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns a ValueAnimator that animates between float values. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * @param values A set of values that the animation will animate between over time.
+     * @return A ValueAnimator object that is set up to animate between the given values.
+     */
+    public static ValueAnimator ofFloat(float... values) {
+        ValueAnimator anim = new ValueAnimator();
+        anim.setFloatValues(values);
+        return anim;
+    }
+
+    /**
+     * Constructs and returns a ValueAnimator that animates between the values
+     * specified in the PropertyValuesHolder objects.
+     *
+     * @param values A set of PropertyValuesHolder objects whose values will be animated
+     * between over time.
+     * @return A ValueAnimator object that is set up to animate between the given values.
+     */
+    public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
+        ValueAnimator anim = new ValueAnimator();
+        anim.setValues(values);
+        return anim;
+    }
+    /**
+     * Constructs and returns a ValueAnimator that animates between Object values. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * <p>Since ValueAnimator does not know how to animate between arbitrary Objects, this
+     * factory method also takes a TypeEvaluator object that the ValueAnimator will use
+     * to perform that interpolation.
+     *
+     * @param evaluator A TypeEvaluator that will be called on each animation frame to
+     * provide the ncessry interpolation between the Object values to derive the animated
+     * value.
+     * @param values A set of values that the animation will animate between over time.
+     * @return A ValueAnimator object that is set up to animate between the given values.
+     */
+    public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
+        ValueAnimator anim = new ValueAnimator();
+        anim.setObjectValues(values);
+        anim.setEvaluator(evaluator);
+        return anim;
+    }
+
+    /**
+     * Sets int values that will be animated between. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+     * than one PropertyValuesHolder object, this method will set the values for the first
+     * of those objects.</p>
+     *
+     * @param values A set of values that the animation will animate between over time.
+     */
+    public void setIntValues(int... values) {
+        if (values == null || values.length == 0) {
+            return;
+        }
+        if (mValues == null || mValues.length == 0) {
+            setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofInt("", values)});
+        } else {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            valuesHolder.setIntValues(values);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Sets float values that will be animated between. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+     * than one PropertyValuesHolder object, this method will set the values for the first
+     * of those objects.</p>
+     *
+     * @param values A set of values that the animation will animate between over time.
+     */
+    public void setFloatValues(float... values) {
+        if (values == null || values.length == 0) {
+            return;
+        }
+        if (mValues == null || mValues.length == 0) {
+            setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofFloat("", values)});
+        } else {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            valuesHolder.setFloatValues(values);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Sets the values to animate between for this animation. A single
+     * value implies that that value is the one being animated to. However, this is not typically
+     * useful in a ValueAnimator object because there is no way for the object to determine the
+     * starting value for the animation (unlike ObjectAnimator, which can derive that value
+     * from the target object and property being animated). Therefore, there should typically
+     * be two or more values.
+     *
+     * <p>If there are already multiple sets of values defined for this ValueAnimator via more
+     * than one PropertyValuesHolder object, this method will set the values for the first
+     * of those objects.</p>
+     *
+     * <p>There should be a TypeEvaluator set on the ValueAnimator that knows how to interpolate
+     * between these value objects. ValueAnimator only knows how to interpolate between the
+     * primitive types specified in the other setValues() methods.</p>
+     *
+     * @param values The set of values to animate between.
+     */
+    public void setObjectValues(Object... values) {
+        if (values == null || values.length == 0) {
+            return;
+        }
+        if (mValues == null || mValues.length == 0) {
+            setValues(new PropertyValuesHolder[]{PropertyValuesHolder.ofObject("",
+                    (TypeEvaluator)null, values)});
+        } else {
+            PropertyValuesHolder valuesHolder = mValues[0];
+            valuesHolder.setObjectValues(values);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Sets the values, per property, being animated between. This function is called internally
+     * by the constructors of ValueAnimator that take a list of values. But an ValueAnimator can
+     * be constructed without values and this method can be called to set the values manually
+     * instead.
+     *
+     * @param values The set of values, per property, being animated between.
+     */
+    public void setValues(PropertyValuesHolder... values) {
+        int numValues = values.length;
+        mValues = values;
+        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+        for (int i = 0; i < numValues; ++i) {
+            PropertyValuesHolder valuesHolder = (PropertyValuesHolder) values[i];
+            mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
+        }
+        // New property/values/target should cause re-initialization prior to starting
+        mInitialized = false;
+    }
+
+    /**
+     * Returns the values that this ValueAnimator animates between. These values are stored in
+     * PropertyValuesHolder objects, even if the ValueAnimator was created with a simple list
+     * of value objects instead.
+     *
+     * @return PropertyValuesHolder[] An array of PropertyValuesHolder objects which hold the
+     * values, per property, that define the animation.
+     */
+    public PropertyValuesHolder[] getValues() {
+        return mValues;
+    }
+
+    /**
+     * This function is called immediately before processing the first animation
+     * frame of an animation. If there is a nonzero <code>startDelay</code>, the
+     * function is called after that delay ends.
+     * It takes care of the final initialization steps for the
+     * animation.
+     *
+     *  <p>Overrides of this method should call the superclass method to ensure
+     *  that internal mechanisms for the animation are set up correctly.</p>
+     */
+    void initAnimation() {
+        if (!mInitialized) {
+            int numValues = mValues.length;
+            for (int i = 0; i < numValues; ++i) {
+                mValues[i].init();
+            }
+            mInitialized = true;
+        }
+    }
+
+
+    /**
+     * Sets the length of the animation. The default duration is 300 milliseconds.
+     *
+     * @param duration The length of the animation, in milliseconds. This value cannot
+     * be negative.
+     * @return ValueAnimator The object called with setDuration(). This return
+     * value makes it easier to compose statements together that construct and then set the
+     * duration, as in <code>ValueAnimator.ofInt(0, 10).setDuration(500).start()</code>.
+     */
+    public ValueAnimator setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    duration);
+        }
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Gets the length of the animation. The default duration is 300 milliseconds.
+     *
+     * @return The length of the animation, in milliseconds.
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Sets the position of the animation to the specified point in time. This time should
+     * be between 0 and the total duration of the animation, including any repetition. If
+     * the animation has not yet been started, then it will not advance forward after it is
+     * set to this time; it will simply set the time to this value and perform any appropriate
+     * actions based on that time. If the animation is already running, then setCurrentPlayTime()
+     * will set the current playing time to this value and continue playing from that point.
+     *
+     * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
+     */
+    public void setCurrentPlayTime(long playTime) {
+        initAnimation();
+        long currentTime = AnimationUtils.currentAnimationTimeMillis();
+        if (mPlayingState != RUNNING) {
+            mSeekTime = playTime;
+            mPlayingState = SEEKED;
+        }
+        mStartTime = currentTime - playTime;
+        animationFrame(currentTime);
+    }
+
+    /**
+     * Gets the current position of the animation in time, which is equal to the current
+     * time minus the time that the animation started. An animation that is not yet started will
+     * return a value of zero.
+     *
+     * @return The current position in time of the animation.
+     */
+    public long getCurrentPlayTime() {
+        if (!mInitialized || mPlayingState == STOPPED) {
+            return 0;
+        }
+        return AnimationUtils.currentAnimationTimeMillis() - mStartTime;
+    }
+
+    /**
+     * This custom, static handler handles the timing pulse that is shared by
+     * all active animations. This approach ensures that the setting of animation
+     * values will happen on the UI thread and that all animations will share
+     * the same times for calculating their values, which makes synchronizing
+     * animations possible.
+     *
+     */
+    private static class AnimationHandler extends Handler {
+        /**
+         * There are only two messages that we care about: ANIMATION_START and
+         * ANIMATION_FRAME. The START message is sent when an animation's start()
+         * method is called. It cannot start synchronously when start() is called
+         * because the call may be on the wrong thread, and it would also not be
+         * synchronized with other animations because it would not start on a common
+         * timing pulse. So each animation sends a START message to the handler, which
+         * causes the handler to place the animation on the active animations queue and
+         * start processing frames for that animation.
+         * The FRAME message is the one that is sent over and over while there are any
+         * active animations to process.
+         */
+        @Override
+        public void handleMessage(Message msg) {
+            boolean callAgain = true;
+            ArrayList<ValueAnimator> animations = sAnimations.get();
+            ArrayList<ValueAnimator> delayedAnims = sDelayedAnims.get();
+            switch (msg.what) {
+                // TODO: should we avoid sending frame message when starting if we
+                // were already running?
+                case ANIMATION_START:
+                    ArrayList<ValueAnimator> pendingAnimations = sPendingAnimations.get();
+                    if (animations.size() > 0 || delayedAnims.size() > 0) {
+                        callAgain = false;
+                    }
+                    // pendingAnims holds any animations that have requested to be started
+                    // We're going to clear sPendingAnimations, but starting animation may
+                    // cause more to be added to the pending list (for example, if one animation
+                    // starting triggers another starting). So we loop until sPendingAnimations
+                    // is empty.
+                    while (pendingAnimations.size() > 0) {
+                        ArrayList<ValueAnimator> pendingCopy =
+                                (ArrayList<ValueAnimator>) pendingAnimations.clone();
+                        pendingAnimations.clear();
+                        int count = pendingCopy.size();
+                        for (int i = 0; i < count; ++i) {
+                            ValueAnimator anim = pendingCopy.get(i);
+                            // If the animation has a startDelay, place it on the delayed list
+                            if (anim.mStartDelay == 0) {
+                                anim.startAnimation();
+                            } else {
+                                delayedAnims.add(anim);
+                            }
+                        }
+                    }
+                    // fall through to process first frame of new animations
+                case ANIMATION_FRAME:
+                    // currentTime holds the common time for all animations processed
+                    // during this frame
+                    long currentTime = AnimationUtils.currentAnimationTimeMillis();
+                    ArrayList<ValueAnimator> readyAnims = sReadyAnims.get();
+                    ArrayList<ValueAnimator> endingAnims = sEndingAnims.get();
+
+                    // First, process animations currently sitting on the delayed queue, adding
+                    // them to the active animations if they are ready
+                    int numDelayedAnims = delayedAnims.size();
+                    for (int i = 0; i < numDelayedAnims; ++i) {
+                        ValueAnimator anim = delayedAnims.get(i);
+                        if (anim.delayedAnimationFrame(currentTime)) {
+                            readyAnims.add(anim);
+                        }
+                    }
+                    int numReadyAnims = readyAnims.size();
+                    if (numReadyAnims > 0) {
+                        for (int i = 0; i < numReadyAnims; ++i) {
+                            ValueAnimator anim = readyAnims.get(i);
+                            anim.startAnimation();
+                            anim.mRunning = true;
+                            delayedAnims.remove(anim);
+                        }
+                        readyAnims.clear();
+                    }
+
+                    // Now process all active animations. The return value from animationFrame()
+                    // tells the handler whether it should now be ended
+                    int numAnims = animations.size();
+                    int i = 0;
+                    while (i < numAnims) {
+                        ValueAnimator anim = animations.get(i);
+                        if (anim.animationFrame(currentTime)) {
+                            endingAnims.add(anim);
+                        }
+                        if (animations.size() == numAnims) {
+                            ++i;
+                        } else {
+                            // An animation might be canceled or ended by client code
+                            // during the animation frame. Check to see if this happened by
+                            // seeing whether the current index is the same as it was before
+                            // calling animationFrame(). Another approach would be to copy
+                            // animations to a temporary list and process that list instead,
+                            // but that entails garbage and processing overhead that would
+                            // be nice to avoid.
+                            --numAnims;
+                            endingAnims.remove(anim);
+                        }
+                    }
+                    if (endingAnims.size() > 0) {
+                        for (i = 0; i < endingAnims.size(); ++i) {
+                            endingAnims.get(i).endAnimation();
+                        }
+                        endingAnims.clear();
+                    }
+
+                    // If there are still active or delayed animations, call the handler again
+                    // after the frameDelay
+                    if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) {
+                        sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay -
+                            (AnimationUtils.currentAnimationTimeMillis() - currentTime)));
+                    }
+                    break;
+            }
+        }
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+     *
+     * @return the number of milliseconds to delay running the animation
+     */
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, to delay starting the animation after
+     * {@link #start()} is called.
+
+     * @param startDelay The amount of the delay, in milliseconds
+     */
+    public void setStartDelay(long startDelay) {
+        this.mStartDelay = startDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation. This is a
+     * requested time that the animation will attempt to honor, but the actual delay between
+     * frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     *
+     * @return the requested time between frames, in milliseconds
+     */
+    public static long getFrameDelay() {
+        return sFrameDelay;
+    }
+
+    /**
+     * The amount of time, in milliseconds, between each frame of the animation. This is a
+     * requested time that the animation will attempt to honor, but the actual delay between
+     * frames may be different, depending on system load and capabilities. This is a static
+     * function because the same delay will be applied to all animations, since they are all
+     * run off of a single timing loop.
+     *
+     * @param frameDelay the requested time between frames, in milliseconds
+     */
+    public static void setFrameDelay(long frameDelay) {
+        sFrameDelay = frameDelay;
+    }
+
+    /**
+     * The most recent value calculated by this <code>ValueAnimator</code> when there is just one
+     * property being animated. This value is only sensible while the animation is running. The main
+     * purpose for this read-only property is to retrieve the value from the <code>ValueAnimator</code>
+     * during a call to {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+     * is called during each animation frame, immediately after the value is calculated.
+     *
+     * @return animatedValue The value most recently calculated by this <code>ValueAnimator</code> for
+     * the single property being animated. If there are several properties being animated
+     * (specified by several PropertyValuesHolder objects in the constructor), this function
+     * returns the animated value for the first of those objects.
+     */
+    public Object getAnimatedValue() {
+        if (mValues != null && mValues.length > 0) {
+            return mValues[0].getAnimatedValue();
+        }
+        // Shouldn't get here; should always have values unless ValueAnimator was set up wrong
+        return null;
+    }
+
+    /**
+     * The most recent value calculated by this <code>ValueAnimator</code> for <code>propertyName</code>.
+     * The main purpose for this read-only property is to retrieve the value from the
+     * <code>ValueAnimator</code> during a call to
+     * {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)}, which
+     * is called during each animation frame, immediately after the value is calculated.
+     *
+     * @return animatedValue The value most recently calculated for the named property
+     * by this <code>ValueAnimator</code>.
+     */
+    public Object getAnimatedValue(String propertyName) {
+        PropertyValuesHolder valuesHolder = mValuesMap.get(propertyName);
+        if (valuesHolder != null) {
+            return valuesHolder.getAnimatedValue();
+        } else {
+            // At least avoid crashing if called with bogus propertyName
+            return null;
+        }
+    }
+
+    /**
+     * Sets how many times the animation should be repeated. If the repeat
+     * count is 0, the animation is never repeated. If the repeat count is
+     * greater than 0 or {@link #INFINITE}, the repeat mode will be taken
+     * into account. The repeat count is 0 by default.
+     *
+     * @param value the number of times the animation should be repeated
+     */
+    public void setRepeatCount(int value) {
+        mRepeatCount = value;
+    }
+    /**
+     * Defines how many times the animation should repeat. The default value
+     * is 0.
+     *
+     * @return the number of times the animation should repeat, or {@link #INFINITE}
+     */
+    public int getRepeatCount() {
+        return mRepeatCount;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end. This
+     * setting is applied only when the repeat count is either greater than
+     * 0 or {@link #INFINITE}. Defaults to {@link #RESTART}.
+     *
+     * @param value {@link #RESTART} or {@link #REVERSE}
+     */
+    public void setRepeatMode(int value) {
+        mRepeatMode = value;
+    }
+
+    /**
+     * Defines what this animation should do when it reaches the end.
+     *
+     * @return either one of {@link #REVERSE} or {@link #RESTART}
+     */
+    public int getRepeatMode() {
+        return mRepeatMode;
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent update events through the life of
+     * an animation. This method is called on all listeners for every frame of the animation,
+     * after the values for the animation have been calculated.
+     *
+     * @param listener the listener to be added to the current set of listeners for this animation.
+     */
+    public void addUpdateListener(AnimatorUpdateListener listener) {
+        if (mUpdateListeners == null) {
+            mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+        }
+        mUpdateListeners.add(listener);
+    }
+
+    /**
+     * Removes all listeners from the set listening to frame updates for this animation.
+     */
+    public void removeAllUpdateListeners() {
+        if (mUpdateListeners == null) {
+            return;
+        }
+        mUpdateListeners.clear();
+        mUpdateListeners = null;
+    }
+
+    /**
+     * Removes a listener from the set listening to frame updates for this animation.
+     *
+     * @param listener the listener to be removed from the current set of update listeners
+     * for this animation.
+     */
+    public void removeUpdateListener(AnimatorUpdateListener listener) {
+        if (mUpdateListeners == null) {
+            return;
+        }
+        mUpdateListeners.remove(listener);
+        if (mUpdateListeners.size() == 0) {
+            mUpdateListeners = null;
+        }
+    }
+
+
+    /**
+     * The time interpolator used in calculating the elapsed fraction of this animation. The
+     * interpolator determines whether the animation runs with linear or non-linear motion,
+     * such as acceleration and deceleration. The default value is
+     * {@link android.view.animation.AccelerateDecelerateInterpolator}
+     *
+     * @param value the interpolator to be used by this animation. A value of <code>null</code>
+     * will result in linear interpolation.
+     */
+    @Override
+    public void setInterpolator(/*Time*/Interpolator value) {
+        if (value != null) {
+            mInterpolator = value;
+        } else {
+            mInterpolator = new LinearInterpolator();
+        }
+    }
+
+    /**
+     * Returns the timing interpolator that this ValueAnimator uses.
+     *
+     * @return The timing interpolator for this ValueAnimator.
+     */
+    public /*Time*/Interpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * The type evaluator to be used when calculating the animated values of this animation.
+     * The system will automatically assign a float or int evaluator based on the type
+     * of <code>startValue</code> and <code>endValue</code> in the constructor. But if these values
+     * are not one of these primitive types, or if different evaluation is desired (such as is
+     * necessary with int values that represent colors), a custom evaluator needs to be assigned.
+     * For example, when running an animation on color values, the {@link ArgbEvaluator}
+     * should be used to get correct RGB color interpolation.
+     *
+     * <p>If this ValueAnimator has only one set of values being animated between, this evaluator
+     * will be used for that set. If there are several sets of values being animated, which is
+     * the case if PropertyValuesHOlder objects were set on the ValueAnimator, then the evaluator
+     * is assigned just to the first PropertyValuesHolder object.</p>
+     *
+     * @param value the evaluator to be used this animation
+     */
+    public void setEvaluator(TypeEvaluator value) {
+        if (value != null && mValues != null && mValues.length > 0) {
+            mValues[0].setEvaluator(value);
+        }
+    }
+
+    /**
+     * Start the animation playing. This version of start() takes a boolean flag that indicates
+     * whether the animation should play in reverse. The flag is usually false, but may be set
+     * to true if called from the reverse() method.
+     *
+     * <p>The animation started by calling this method will be run on the thread that called
+     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+     * this is not the case). Also, if the animation will animate
+     * properties of objects in the view hierarchy, then the calling thread should be the UI
+     * thread for that view hierarchy.</p>
+     *
+     * @param playBackwards Whether the ValueAnimator should start playing in reverse.
+     */
+    private void start(boolean playBackwards) {
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
+        }
+        mPlayingBackwards = playBackwards;
+        mCurrentIteration = 0;
+        mPlayingState = STOPPED;
+        mStarted = true;
+        mStartedDelay = false;
+        sPendingAnimations.get().add(this);
+        if (mStartDelay == 0) {
+            // This sets the initial value of the animation, prior to actually starting it running
+            setCurrentPlayTime(getCurrentPlayTime());
+            mPlayingState = STOPPED;
+            mRunning = true;
+
+            if (mListeners != null) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationStart(this);
+                }
+            }
+        }
+        AnimationHandler animationHandler = sAnimationHandler.get();
+        if (animationHandler == null) {
+            animationHandler = new AnimationHandler();
+            sAnimationHandler.set(animationHandler);
+        }
+        animationHandler.sendEmptyMessage(ANIMATION_START);
+    }
+
+    @Override
+    public void start() {
+        start(false);
+    }
+
+    @Override
+    public void cancel() {
+        // Only cancel if the animation is actually running or has been started and is about
+        // to run
+        if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) ||
+                sDelayedAnims.get().contains(this)) {
+            // Only notify listeners if the animator has actually started
+            if (mRunning && mListeners != null) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                for (AnimatorListener listener : tmpListeners) {
+                    listener.onAnimationCancel(this);
+                }
+            }
+            endAnimation();
+        }
+    }
+
+    @Override
+    public void end() {
+        if (!sAnimations.get().contains(this) && !sPendingAnimations.get().contains(this)) {
+            // Special case if the animation has not yet started; get it ready for ending
+            mStartedDelay = false;
+            startAnimation();
+        } else if (!mInitialized) {
+            initAnimation();
+        }
+        // The final value set on the target varies, depending on whether the animation
+        // was supposed to repeat an odd number of times
+        if (mRepeatCount > 0 && (mRepeatCount & 0x01) == 1) {
+            animateValue(0f);
+        } else {
+            animateValue(1f);
+        }
+        endAnimation();
+    }
+
+    @Override
+    public boolean isRunning() {
+        return (mPlayingState == RUNNING || mRunning);
+    }
+
+    @Override
+    public boolean isStarted() {
+        return mStarted;
+    }
+
+    /**
+     * Plays the ValueAnimator in reverse. If the animation is already running,
+     * it will stop itself and play backwards from the point reached when reverse was called.
+     * If the animation is not currently running, then it will start from the end and
+     * play backwards. This behavior is only set for the current animation; future playing
+     * of the animation will use the default behavior of playing forward.
+     */
+    public void reverse() {
+        mPlayingBackwards = !mPlayingBackwards;
+        if (mPlayingState == RUNNING) {
+            long currentTime = AnimationUtils.currentAnimationTimeMillis();
+            long currentPlayTime = currentTime - mStartTime;
+            long timeLeft = mDuration - currentPlayTime;
+            mStartTime = currentTime - timeLeft;
+        } else {
+            start(true);
+        }
+    }
+
+    /**
+     * Called internally to end an animation by removing it from the animations list. Must be
+     * called on the UI thread.
+     */
+    private void endAnimation() {
+        sAnimations.get().remove(this);
+        sPendingAnimations.get().remove(this);
+        sDelayedAnims.get().remove(this);
+        mPlayingState = STOPPED;
+        if (mRunning && mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this);
+            }
+        }
+        mRunning = false;
+        mStarted = false;
+    }
+
+    /**
+     * Called internally to start an animation by adding it to the active animations list. Must be
+     * called on the UI thread.
+     */
+    private void startAnimation() {
+        initAnimation();
+        sAnimations.get().add(this);
+        if (mStartDelay > 0 && mListeners != null) {
+            // Listeners were already notified in start() if startDelay is 0; this is
+            // just for delayed animations
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationStart(this);
+            }
+        }
+    }
+
+    /**
+     * Internal function called to process an animation frame on an animation that is currently
+     * sleeping through its <code>startDelay</code> phase. The return value indicates whether it
+     * should be woken up and put on the active animations queue.
+     *
+     * @param currentTime The current animation time, used to calculate whether the animation
+     * has exceeded its <code>startDelay</code> and should be started.
+     * @return True if the animation's <code>startDelay</code> has been exceeded and the animation
+     * should be added to the set of active animations.
+     */
+    private boolean delayedAnimationFrame(long currentTime) {
+        if (!mStartedDelay) {
+            mStartedDelay = true;
+            mDelayStartTime = currentTime;
+        } else {
+            long deltaTime = currentTime - mDelayStartTime;
+            if (deltaTime > mStartDelay) {
+                // startDelay ended - start the anim and record the
+                // mStartTime appropriately
+                mStartTime = currentTime - (deltaTime - mStartDelay);
+                mPlayingState = RUNNING;
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * This internal function processes a single animation frame for a given animation. The
+     * currentTime parameter is the timing pulse sent by the handler, used to calculate the
+     * elapsed duration, and therefore
+     * the elapsed fraction, of the animation. The return value indicates whether the animation
+     * should be ended (which happens when the elapsed time of the animation exceeds the
+     * animation's duration, including the repeatCount).
+     *
+     * @param currentTime The current time, as tracked by the static timing handler
+     * @return true if the animation's duration, including any repetitions due to
+     * <code>repeatCount</code> has been exceeded and the animation should be ended.
+     */
+    boolean animationFrame(long currentTime) {
+        boolean done = false;
+
+        if (mPlayingState == STOPPED) {
+            mPlayingState = RUNNING;
+            if (mSeekTime < 0) {
+                mStartTime = currentTime;
+            } else {
+                mStartTime = currentTime - mSeekTime;
+                // Now that we're playing, reset the seek time
+                mSeekTime = -1;
+            }
+        }
+        switch (mPlayingState) {
+        case RUNNING:
+        case SEEKED:
+            float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
+            if (fraction >= 1f) {
+                if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
+                    // Time to repeat
+                    if (mListeners != null) {
+                        int numListeners = mListeners.size();
+                        for (int i = 0; i < numListeners; ++i) {
+                            mListeners.get(i).onAnimationRepeat(this);
+                        }
+                    }
+                    if (mRepeatMode == REVERSE) {
+                        mPlayingBackwards = mPlayingBackwards ? false : true;
+                    }
+                    mCurrentIteration += (int)fraction;
+                    fraction = fraction % 1f;
+                    mStartTime += mDuration;
+                } else {
+                    done = true;
+                    fraction = Math.min(fraction, 1.0f);
+                }
+            }
+            if (mPlayingBackwards) {
+                fraction = 1f - fraction;
+            }
+            animateValue(fraction);
+            break;
+        }
+
+        return done;
+    }
+
+    /**
+     * Returns the current animation fraction, which is the elapsed/interpolated fraction used in
+     * the most recent frame update on the animation.
+     *
+     * @return Elapsed/interpolated fraction of the animation.
+     */
+    public float getAnimatedFraction() {
+        return mCurrentFraction;
+    }
+
+    /**
+     * This method is called with the elapsed fraction of the animation during every
+     * animation frame. This function turns the elapsed fraction into an interpolated fraction
+     * and then into an animated value (from the evaluator. The function is called mostly during
+     * animation updates, but it is also called when the <code>end()</code>
+     * function is called, to set the final value on the property.
+     *
+     * <p>Overrides of this method must call the superclass to perform the calculation
+     * of the animated value.</p>
+     *
+     * @param fraction The elapsed fraction of the animation.
+     */
+    void animateValue(float fraction) {
+        fraction = mInterpolator.getInterpolation(fraction);
+        mCurrentFraction = fraction;
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].calculateValue(fraction);
+        }
+        if (mUpdateListeners != null) {
+            int numListeners = mUpdateListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                mUpdateListeners.get(i).onAnimationUpdate(this);
+            }
+        }
+    }
+
+    @Override
+    public ValueAnimator clone() {
+        final ValueAnimator anim = (ValueAnimator) super.clone();
+        if (mUpdateListeners != null) {
+            ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
+            anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
+            int numListeners = oldListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                anim.mUpdateListeners.add(oldListeners.get(i));
+            }
+        }
+        anim.mSeekTime = -1;
+        anim.mPlayingBackwards = false;
+        anim.mCurrentIteration = 0;
+        anim.mInitialized = false;
+        anim.mPlayingState = STOPPED;
+        anim.mStartedDelay = false;
+        PropertyValuesHolder[] oldValues = mValues;
+        if (oldValues != null) {
+            int numValues = oldValues.length;
+            anim.mValues = new PropertyValuesHolder[numValues];
+            anim.mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+            for (int i = 0; i < numValues; ++i) {
+                PropertyValuesHolder newValuesHolder = oldValues[i].clone();
+                anim.mValues[i] = newValuesHolder;
+                anim.mValuesMap.put(newValuesHolder.getPropertyName(), newValuesHolder);
+            }
+        }
+        return anim;
+    }
+
+    /**
+     * Implementors of this interface can add themselves as update listeners
+     * to an <code>ValueAnimator</code> instance to receive callbacks on every animation
+     * frame, after the current frame's values have been calculated for that
+     * <code>ValueAnimator</code>.
+     */
+    public static interface AnimatorUpdateListener {
+        /**
+         * <p>Notifies the occurrence of another frame of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationUpdate(ValueAnimator animation);
+
+    }
+
+    /**
+     * Return the number of animations currently running.
+     *
+     * Used by StrictMode internally to annotate violations.  Only
+     * called on the main thread.
+     *
+     * @hide
+     */
+    public static int getCurrentAnimationsCount() {
+        return sAnimations.get().size();
+    }
+
+    /**
+     * Clear all animations on this thread, without canceling or ending them.
+     * This should be used with caution.
+     *
+     * @hide
+     */
+    public static void clearAllAnimations() {
+        sAnimations.get().clear();
+        sPendingAnimations.get().clear();
+        sDelayedAnims.get().clear();
+    }
+
+    @Override
+    public String toString() {
+        String returnVal = "ValueAnimator@" + Integer.toHexString(hashCode());
+        if (mValues != null) {
+            for (int i = 0; i < mValues.length; ++i) {
+                returnVal += "\n    " + mValues[i].toString();
+            }
+        }
+        return returnVal;
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/util/FloatProperty.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.util;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>float</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #set(Object, Float) set()} function that takes the primitive
+ * <code>float</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Float</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ *
+ * @hide
+ */
+public abstract class FloatProperty<T> extends Property<T, Float> {
+
+    public FloatProperty(String name) {
+        super(Float.class, name);
+    }
+
+    /**
+     * A type-specific override of the {@link #set(Object, Float)} that is faster when dealing
+     * with fields of type <code>float</code>.
+     */
+    public abstract void setValue(T object, float value);
+
+    @Override
+    final public void set(T object, Float value) {
+        setValue(object, value);
+    }
+
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/util/IntProperty.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.util;
+
+/**
+ * An implementation of {@link android.util.Property} to be used specifically with fields of type
+ * <code>int</code>. This type-specific subclass enables performance benefit by allowing
+ * calls to a {@link #set(Object, Integer) set()} function that takes the primitive
+ * <code>int</code> type and avoids autoboxing and other overhead associated with the
+ * <code>Integer</code> class.
+ *
+ * @param <T> The class on which the Property is declared.
+ *
+ * @hide
+ */
+public abstract class IntProperty<T> extends Property<T, Integer> {
+
+    public IntProperty(String name) {
+        super(Integer.class, name);
+    }
+
+    /**
+     * A type-specific override of the {@link #set(Object, Integer)} that is faster when dealing
+     * with fields of type <code>int</code>.
+     */
+    public abstract void setValue(T object, int value);
+
+    @Override
+    final public void set(T object, Integer value) {
+        set(object, value.intValue());
+    }
+
+}
\ No newline at end of file
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/util/NoSuchPropertyException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.util;
+
+/**
+ * Thrown when code requests a {@link Property} on a class that does
+ * not expose the appropriate method or field.
+ *
+ * @see Property#of(java.lang.Class, java.lang.Class, java.lang.String)
+ */
+public class NoSuchPropertyException extends RuntimeException {
+
+    public NoSuchPropertyException(String s) {
+        super(s);
+    }
+
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/util/Property.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.util;
+
+
+/**
+ * A property is an abstraction that can be used to represent a <emb>mutable</em> value that is held
+ * in a <em>host</em> object. The Property's {@link #set(Object, Object)} or {@link #get(Object)}
+ * methods can be implemented in terms of the private fields of the host object, or via "setter" and
+ * "getter" methods or by some other mechanism, as appropriate.
+ *
+ * @param <T> The class on which the property is declared.
+ * @param <V> The type that this property represents.
+ */
+public abstract class Property<T, V> {
+
+    private final String mName;
+    private final Class<V> mType;
+
+    /**
+     * This factory method creates and returns a Property given the <code>class</code> and
+     * <code>name</code> parameters, where the <code>"name"</code> parameter represents either:
+     * <ul>
+     *     <li>a public <code>getName()</code> method on the class which takes no arguments, plus an
+     *     optional public <code>setName()</code> method which takes a value of the same type
+     *     returned by <code>getName()</code>
+     *     <li>a public <code>isName()</code> method on the class which takes no arguments, plus an
+     *     optional public <code>setName()</code> method which takes a value of the same type
+     *     returned by <code>isName()</code>
+     *     <li>a public <code>name</code> field on the class
+     * </ul>
+     *
+     * <p>If either of the get/is method alternatives is found on the class, but an appropriate
+     * <code>setName()</code> method is not found, the <code>Property</code> will be
+     * {@link #isReadOnly() readOnly}. Calling the {@link #set(Object, Object)} method on such
+     * a property is allowed, but will have no effect.</p>
+     *
+     * <p>If neither the methods nor the field are found on the class a
+     * {@link NoSuchPropertyException} exception will be thrown.</p>
+     */
+    public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
+        return new ReflectiveProperty<T, V>(hostType, valueType, name);
+    }
+
+    /**
+     * A constructor that takes an identifying name and {@link #getType() type} for the property.
+     */
+    public Property(Class<V> type, String name) {
+        mName = name;
+        mType = type;
+    }
+
+    /**
+     * Returns true if the {@link #set(Object, Object)} method does not set the value on the target
+     * object (in which case the {@link #set(Object, Object) set()} method should throw a {@link
+     * NoSuchPropertyException} exception). This may happen if the Property wraps functionality that
+     * allows querying the underlying value but not setting it. For example, the {@link #of(Class,
+     * Class, String)} factory method may return a Property with name "foo" for an object that has
+     * only a <code>getFoo()</code> or <code>isFoo()</code> method, but no matching
+     * <code>setFoo()</code> method.
+     */
+    public boolean isReadOnly() {
+        return false;
+    }
+
+    /**
+     * Sets the value on <code>object</code> which this property represents. If the method is unable
+     * to set the value on the target object it will throw an {@link UnsupportedOperationException}
+     * exception.
+     */
+    public void set(T object, V value) {
+        throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+    }
+
+    /**
+     * Returns the current value that this property represents on the given <code>object</code>.
+     */
+    public abstract V get(T object);
+
+    /**
+     * Returns the name for this property.
+     */
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the type for this property.
+     */
+    public Class<V> getType() {
+        return mType;
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/util/ReflectiveProperty.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.nineoldandroids.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Internal class to automatically generate a Property for a given class/name pair, given the
+ * specification of {@link Property#of(java.lang.Class, java.lang.Class, java.lang.String)}
+ */
+class ReflectiveProperty<T, V> extends Property<T, V> {
+
+    private static final String PREFIX_GET = "get";
+    private static final String PREFIX_IS = "is";
+    private static final String PREFIX_SET = "set";
+    private Method mSetter;
+    private Method mGetter;
+    private Field mField;
+
+    /**
+     * For given property name 'name', look for getName/isName method or 'name' field.
+     * Also look for setName method (optional - could be readonly). Failing method getters and
+     * field results in throwing NoSuchPropertyException.
+     *
+     * @param propertyHolder The class on which the methods or field are found
+     * @param name The name of the property, where this name is capitalized and appended to
+     * "get" and "is to search for the appropriate methods. If the get/is methods are not found,
+     * the constructor will search for a field with that exact name.
+     */
+    public ReflectiveProperty(Class<T> propertyHolder, Class<V> valueType, String name) {
+         // TODO: cache reflection info for each new class/name pair
+        super(valueType, name);
+        char firstLetter = Character.toUpperCase(name.charAt(0));
+        String theRest = name.substring(1);
+        String capitalizedName = firstLetter + theRest;
+        String getterName = PREFIX_GET + capitalizedName;
+        try {
+            mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
+        } catch (NoSuchMethodException e) {
+            try {
+                /* The native implementation uses JNI to do reflection, which allows access to private methods.
+                 * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
+                 */
+                mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
+                mGetter.setAccessible(true);
+            } catch (NoSuchMethodException e2) {
+                // getName() not available - try isName() instead
+                getterName = PREFIX_IS + capitalizedName;
+                try {
+                    mGetter = propertyHolder.getMethod(getterName, (Class<?>[]) null);
+                } catch (NoSuchMethodException e3) {
+                    try {
+                        /* The native implementation uses JNI to do reflection, which allows access to private methods.
+                         * getDeclaredMethod(..) does not find superclass methods, so it's implemented as a fallback.
+                         */
+                        mGetter = propertyHolder.getDeclaredMethod(getterName, (Class<?>[]) null);
+                        mGetter.setAccessible(true);
+                    } catch (NoSuchMethodException e4) {
+                        // Try public field instead
+                        try {
+                            mField = propertyHolder.getField(name);
+                            Class fieldType = mField.getType();
+                            if (!typesMatch(valueType, fieldType)) {
+                                throw new NoSuchPropertyException("Underlying type (" + fieldType + ") " +
+                                        "does not match Property type (" + valueType + ")");
+                            }
+                            return;
+                        } catch (NoSuchFieldException e5) {
+                            // no way to access property - throw appropriate exception
+                            throw new NoSuchPropertyException("No accessor method or field found for"
+                                    + " property with name " + name);
+                        }
+                    }
+                }
+            }
+        }
+        Class getterType = mGetter.getReturnType();
+        // Check to make sure our getter type matches our valueType
+        if (!typesMatch(valueType, getterType)) {
+            throw new NoSuchPropertyException("Underlying type (" + getterType + ") " +
+                    "does not match Property type (" + valueType + ")");
+        }
+        String setterName = PREFIX_SET + capitalizedName;
+        try {
+            // mSetter = propertyHolder.getMethod(setterName, getterType);
+            // The native implementation uses JNI to do reflection, which allows access to private methods.
+            mSetter = propertyHolder.getDeclaredMethod(setterName, getterType);
+            mSetter.setAccessible(true);
+        } catch (NoSuchMethodException ignored) {
+            // Okay to not have a setter - just a readonly property
+        }
+    }
+
+    /**
+     * Utility method to check whether the type of the underlying field/method on the target
+     * object matches the type of the Property. The extra checks for primitive types are because
+     * generics will force the Property type to be a class, whereas the type of the underlying
+     * method/field will probably be a primitive type instead. Accept float as matching Float,
+     * etc.
+     */
+    private boolean typesMatch(Class<V> valueType, Class getterType) {
+        if (getterType != valueType) {
+            if (getterType.isPrimitive()) {
+                return (getterType == float.class && valueType == Float.class) ||
+                        (getterType == int.class && valueType == Integer.class) ||
+                        (getterType == boolean.class && valueType == Boolean.class) ||
+                        (getterType == long.class && valueType == Long.class) ||
+                        (getterType == double.class && valueType == Double.class) ||
+                        (getterType == short.class && valueType == Short.class) ||
+                        (getterType == byte.class && valueType == Byte.class) ||
+                        (getterType == char.class && valueType == Character.class);
+            }
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void set(T object, V value) {
+        if (mSetter != null) {
+            try {
+                mSetter.invoke(object, value);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        } else if (mField != null) {
+            try {
+                mField.set(object, value);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        } else {
+            throw new UnsupportedOperationException("Property " + getName() +" is read-only");
+        }
+    }
+
+    @Override
+    public V get(T object) {
+        if (mGetter != null) {
+            try {
+                return (V) mGetter.invoke(object, (Object[])null);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        } else if (mField != null) {
+            try {
+                return (V) mField.get(object);
+            } catch (IllegalAccessException e) {
+                throw new AssertionError();
+            }
+        }
+        // Should not get here: there should always be a non-null getter or field
+        throw new AssertionError();
+    }
+
+    /**
+     * Returns false if there is no setter or public field underlying this Property.
+     */
+    @Override
+    public boolean isReadOnly() {
+        return (mSetter == null && mField == null);
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewHelper.java
@@ -0,0 +1,292 @@
+package com.nineoldandroids.view;
+
+import android.view.View;
+
+import static com.nineoldandroids.view.animation.AnimatorProxy.NEEDS_PROXY;
+import static com.nineoldandroids.view.animation.AnimatorProxy.wrap;
+
+public final class ViewHelper {
+    private ViewHelper() {}
+
+    public static float getAlpha(View view) {
+        return NEEDS_PROXY ? wrap(view).getAlpha() : Honeycomb.getAlpha(view);
+    }
+
+    public static void setAlpha(View view, float alpha) {
+        if (NEEDS_PROXY) {
+            wrap(view).setAlpha(alpha);
+        } else {
+            Honeycomb.setAlpha(view, alpha);
+        }
+    }
+
+    public static float getPivotX(View view) {
+        return NEEDS_PROXY ? wrap(view).getPivotX() : Honeycomb.getPivotX(view);
+    }
+
+    public static void setPivotX(View view, float pivotX) {
+        if (NEEDS_PROXY) {
+            wrap(view).setPivotX(pivotX);
+        } else {
+            Honeycomb.setPivotX(view, pivotX);
+        }
+    }
+
+    public static float getPivotY(View view) {
+        return NEEDS_PROXY ? wrap(view).getPivotY() : Honeycomb.getPivotY(view);
+    }
+
+    public static void setPivotY(View view, float pivotY) {
+        if (NEEDS_PROXY) {
+            wrap(view).setPivotY(pivotY);
+        } else {
+            Honeycomb.setPivotY(view, pivotY);
+        }
+    }
+
+    public static float getRotation(View view) {
+        return NEEDS_PROXY ? wrap(view).getRotation() : Honeycomb.getRotation(view);
+    }
+
+    public static void setRotation(View view, float rotation) {
+        if (NEEDS_PROXY) {
+            wrap(view).setRotation(rotation);
+        } else {
+            Honeycomb.setRotation(view, rotation);
+        }
+    }
+
+    public static float getRotationX(View view) {
+        return NEEDS_PROXY ? wrap(view).getRotationX() : Honeycomb.getRotationX(view);
+    }
+
+    public static void setRotationX(View view, float rotationX) {
+        if (NEEDS_PROXY) {
+            wrap(view).setRotationX(rotationX);
+        } else {
+            Honeycomb.setRotationX(view, rotationX);
+        }
+    }
+
+    public static float getRotationY(View view) {
+        return NEEDS_PROXY ? wrap(view).getRotationY() : Honeycomb.getRotationY(view);
+    }
+
+    public static void setRotationY(View view, float rotationY) {
+        if (NEEDS_PROXY) {
+            wrap(view).setRotationY(rotationY);
+        } else {
+            Honeycomb.setRotationY(view, rotationY);
+        }
+    }
+
+    public static float getScaleX(View view) {
+        return NEEDS_PROXY ? wrap(view).getScaleX() : Honeycomb.getScaleX(view);
+    }
+
+    public static void setScaleX(View view, float scaleX) {
+        if (NEEDS_PROXY) {
+            wrap(view).setScaleX(scaleX);
+        } else {
+            Honeycomb.setScaleX(view, scaleX);
+        }
+    }
+
+    public static float getScaleY(View view) {
+        return NEEDS_PROXY ? wrap(view).getScaleY() : Honeycomb.getScaleY(view);
+    }
+
+    public static void setScaleY(View view, float scaleY) {
+        if (NEEDS_PROXY) {
+            wrap(view).setScaleY(scaleY);
+        } else {
+            Honeycomb.setScaleY(view, scaleY);
+        }
+    }
+
+    public static float getScrollX(View view) {
+        return NEEDS_PROXY ? wrap(view).getScrollX() : Honeycomb.getScrollX(view);
+    }
+
+    public static void setScrollX(View view, int scrollX) {
+        if (NEEDS_PROXY) {
+            wrap(view).setScrollX(scrollX);
+        } else {
+            Honeycomb.setScrollX(view, scrollX);
+        }
+    }
+
+    public static float getScrollY(View view) {
+        return NEEDS_PROXY ? wrap(view).getScrollY() : Honeycomb.getScrollY(view);
+    }
+
+    public static void setScrollY(View view, int scrollY) {
+        if (NEEDS_PROXY) {
+            wrap(view).setScrollY(scrollY);
+        } else {
+            Honeycomb.setScrollY(view, scrollY);
+        }
+    }
+
+    public static float getTranslationX(View view) {
+        return NEEDS_PROXY ? wrap(view).getTranslationX() : Honeycomb.getTranslationX(view);
+    }
+
+    public static void setTranslationX(View view, float translationX) {
+        if (NEEDS_PROXY) {
+            wrap(view).setTranslationX(translationX);
+        } else {
+            Honeycomb.setTranslationX(view, translationX);
+        }
+    }
+
+    public static float getTranslationY(View view) {
+        return NEEDS_PROXY ? wrap(view).getTranslationY() : Honeycomb.getTranslationY(view);
+    }
+
+    public static void setTranslationY(View view, float translationY) {
+        if (NEEDS_PROXY) {
+            wrap(view).setTranslationY(translationY);
+        } else {
+            Honeycomb.setTranslationY(view, translationY);
+        }
+    }
+
+    public static float getX(View view) {
+        return NEEDS_PROXY ? wrap(view).getX() : Honeycomb.getX(view);
+    }
+
+    public static void setX(View view, float x) {
+        if (NEEDS_PROXY) {
+            wrap(view).setX(x);
+        } else {
+            Honeycomb.setX(view, x);
+        }
+    }
+
+    public static float getY(View view) {
+        return NEEDS_PROXY ? wrap(view).getY() : Honeycomb.getY(view);
+    }
+
+    public static void setY(View view, float y) {
+        if (NEEDS_PROXY) {
+            wrap(view).setY(y);
+        } else {
+            Honeycomb.setY(view, y);
+        }
+    }
+
+    private static final class Honeycomb {
+        static float getAlpha(View view) {
+            return view.getAlpha();
+        }
+
+        static void setAlpha(View view, float alpha) {
+            view.setAlpha(alpha);
+        }
+
+        static float getPivotX(View view) {
+            return view.getPivotX();
+        }
+
+        static void setPivotX(View view, float pivotX) {
+            view.setPivotX(pivotX);
+        }
+
+        static float getPivotY(View view) {
+            return view.getPivotY();
+        }
+
+        static void setPivotY(View view, float pivotY) {
+            view.setPivotY(pivotY);
+        }
+
+        static float getRotation(View view) {
+            return view.getRotation();
+        }
+
+        static void setRotation(View view, float rotation) {
+            view.setRotation(rotation);
+        }
+
+        static float getRotationX(View view) {
+            return view.getRotationX();
+        }
+
+        static void setRotationX(View view, float rotationX) {
+            view.setRotationX(rotationX);
+        }
+
+        static float getRotationY(View view) {
+            return view.getRotationY();
+        }
+
+        static void setRotationY(View view, float rotationY) {
+            view.setRotationY(rotationY);
+        }
+
+        static float getScaleX(View view) {
+            return view.getScaleX();
+        }
+
+        static void setScaleX(View view, float scaleX) {
+            view.setScaleX(scaleX);
+        }
+
+        static float getScaleY(View view) {
+            return view.getScaleY();
+        }
+
+        static void setScaleY(View view, float scaleY) {
+            view.setScaleY(scaleY);
+        }
+
+        static float getScrollX(View view) {
+            return view.getScrollX();
+        }
+
+        static void setScrollX(View view, int scrollX) {
+            view.setScrollX(scrollX);
+        }
+
+        static float getScrollY(View view) {
+            return view.getScrollY();
+        }
+
+        static void setScrollY(View view, int scrollY) {
+            view.setScrollY(scrollY);
+        }
+
+        static float getTranslationX(View view) {
+            return view.getTranslationX();
+        }
+
+        static void setTranslationX(View view, float translationX) {
+            view.setTranslationX(translationX);
+        }
+
+        static float getTranslationY(View view) {
+            return view.getTranslationY();
+        }
+
+        static void setTranslationY(View view, float translationY) {
+            view.setTranslationY(translationY);
+        }
+
+        static float getX(View view) {
+            return view.getX();
+        }
+
+        static void setX(View view, float x) {
+            view.setX(x);
+        }
+
+        static float getY(View view) {
+            return view.getY();
+        }
+
+        static void setY(View view, float y) {
+            view.setY(y);
+        }
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimator.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.view;
+
+import java.util.WeakHashMap;
+import android.os.Build;
+import android.view.View;
+import android.view.animation.Interpolator;
+import com.nineoldandroids.animation.Animator;
+
+/**
+ * This class enables automatic and optimized animation of select properties on View objects.
+ * If only one or two properties on a View object are being animated, then using an
+ * {@link android.animation.ObjectAnimator} is fine; the property setters called by ObjectAnimator
+ * are well equipped to do the right thing to set the property and invalidate the view
+ * appropriately. But if several properties are animated simultaneously, or if you just want a
+ * more convenient syntax to animate a specific property, then ViewPropertyAnimator might be
+ * more well-suited to the task.
+ *
+ * <p>This class may provide better performance for several simultaneous animations, because
+ * it will optimize invalidate calls to take place only once for several properties instead of each
+ * animated property independently causing its own invalidation. Also, the syntax of using this
+ * class could be easier to use because the caller need only tell the View object which
+ * property to animate, and the value to animate either to or by, and this class handles the
+ * details of configuring the underlying Animator class and starting it.</p>
+ *
+ * <p>This class is not constructed by the caller, but rather by the View whose properties
+ * it will animate. Calls to {@link android.view.View#animate()} will return a reference
+ * to the appropriate ViewPropertyAnimator object for that View.</p>
+ *
+ */
+public abstract class ViewPropertyAnimator {
+    private static final WeakHashMap<View, ViewPropertyAnimator> ANIMATORS =
+            new WeakHashMap<View, ViewPropertyAnimator>(0);
+
+    /**
+     * This method returns a ViewPropertyAnimator object, which can be used to animate specific
+     * properties on this View.
+     *
+     * @param view View to animate.
+     * @return The ViewPropertyAnimator associated with this View.
+     */
+    public static ViewPropertyAnimator animate(View view) {
+        ViewPropertyAnimator animator = ANIMATORS.get(view);
+        if (animator == null) {
+            final int version = Integer.valueOf(Build.VERSION.SDK);
+            if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                animator = new ViewPropertyAnimatorICS(view);
+            } else if (version >= Build.VERSION_CODES.HONEYCOMB) {
+                animator = new ViewPropertyAnimatorHC(view);
+            } else {
+                animator = new ViewPropertyAnimatorPreHC(view);
+            }
+            ANIMATORS.put(view, animator);
+        }
+        return animator;
+    }
+
+
+    /**
+     * Sets the duration for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param duration The length of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator setDuration(long duration);
+
+    /**
+     * Returns the current duration of property animations. If the duration was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setDuration(long)
+     * @return The duration of animations, in milliseconds.
+     */
+    public abstract long getDuration();
+
+    /**
+     * Returns the current startDelay of property animations. If the startDelay was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setStartDelay(long)
+     * @return The startDelay of animations, in milliseconds.
+     */
+    public abstract long getStartDelay();
+
+    /**
+     * Sets the startDelay for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param startDelay The delay of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator setStartDelay(long startDelay);
+
+    /**
+     * Sets the interpolator for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default interpolator for ValueAnimator. Calling this method
+     * will cause the declared object to be used instead.
+     *
+     * @param interpolator The TimeInterpolator to be used for ensuing property animations.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator);
+
+    /**
+     * Sets a listener for events in the underlying Animators that run the property
+     * animations.
+     *
+     * @param listener The listener to be called with AnimatorListener events.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator setListener(Animator.AnimatorListener listener);
+
+    /**
+     * Starts the currently pending property animations immediately. Calling <code>start()</code>
+     * is optional because all animations start automatically at the next opportunity. However,
+     * if the animations are needed to start immediately and synchronously (not at the time when
+     * the next event is processed by the hierarchy, which is when the animations would begin
+     * otherwise), then this method can be used.
+     */
+    public abstract void start();
+
+    /**
+     * Cancels all property animations that are currently running or pending.
+     */
+    public abstract void cancel();
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator x(float value);
+
+    /**
+     * This method will cause the View's <code>x</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator xBy(float value);
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator y(float value);
+
+    /**
+     * This method will cause the View's <code>y</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator yBy(float value);
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotation(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotation(float value);
+
+    /**
+     * This method will cause the View's <code>rotation</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotation(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotationBy(float value);
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotationX(float value);
+
+    /**
+     * This method will cause the View's <code>rotationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotationXBy(float value);
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setRotationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotationY(float value);
+
+    /**
+     * This method will cause the View's <code>rotationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setRotationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator rotationYBy(float value);
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setTranslationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator translationX(float value);
+
+    /**
+     * This method will cause the View's <code>translationX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setTranslationX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator translationXBy(float value);
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setTranslationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator translationY(float value);
+
+    /**
+     * This method will cause the View's <code>translationY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setTranslationY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator translationYBy(float value);
+
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setScaleX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator scaleX(float value);
+
+    /**
+     * This method will cause the View's <code>scaleX</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setScaleX(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator scaleXBy(float value);
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setScaleY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator scaleY(float value);
+
+    /**
+     * This method will cause the View's <code>scaleY</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setScaleY(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator scaleYBy(float value);
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated to the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The value to be animated to.
+     * @see View#setAlpha(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator alpha(float value);
+
+    /**
+     * This method will cause the View's <code>alpha</code> property to be animated by the
+     * specified value. Animations already running on the property will be canceled.
+     *
+     * @param value The amount to be animated by, as an offset from the current value.
+     * @see View#setAlpha(float)
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public abstract ViewPropertyAnimator alphaBy(float value);
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorHC.java
@@ -0,0 +1,723 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.view;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import android.view.View;
+import android.view.animation.Interpolator;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.ValueAnimator;
+
+class ViewPropertyAnimatorHC extends ViewPropertyAnimator {
+
+    /**
+     * A WeakReference holding the View whose properties are being animated by this class.
+     * This is set at construction time.
+     */
+    private final WeakReference<View> mView;
+
+    /**
+     * The duration of the underlying Animator object. By default, we don't set the duration
+     * on the Animator and just use its default duration. If the duration is ever set on this
+     * Animator, then we use the duration that it was set to.
+     */
+    private long mDuration;
+
+    /**
+     * A flag indicating whether the duration has been set on this object. If not, we don't set
+     * the duration on the underlying Animator, but instead just use its default duration.
+     */
+    private boolean mDurationSet = false;
+
+    /**
+     * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+     * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+     * Animator, then we use the startDelay that it was set to.
+     */
+    private long mStartDelay = 0;
+
+    /**
+     * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+     * the startDelay on the underlying Animator, but instead just use its default startDelay.
+     */
+    private boolean mStartDelaySet = false;
+
+    /**
+     * The interpolator of the underlying Animator object. By default, we don't set the interpolator
+     * on the Animator and just use its default interpolator. If the interpolator is ever set on
+     * this Animator, then we use the interpolator that it was set to.
+     */
+    private /*Time*/Interpolator mInterpolator;
+
+    /**
+     * A flag indicating whether the interpolator has been set on this object. If not, we don't set
+     * the interpolator on the underlying Animator, but instead just use its default interpolator.
+     */
+    private boolean mInterpolatorSet = false;
+
+    /**
+     * Listener for the lifecycle events of the underlying
+     */
+    private Animator.AnimatorListener mListener = null;
+
+    /**
+     * 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.
+     */
+    private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
+
+    /**
+     * This list holds the properties that have been asked to animate. We allow the caller to
+     * request several animations prior to actually starting the underlying animator. This
+     * enables us to run one single animator to handle several properties in parallel. Each
+     * property is tossed onto the pending list until the animation actually starts (which is
+     * done by posting it onto mView), at which time the pending list is cleared and the properties
+     * on that list are added to the list of properties associated with that animator.
+     */
+    ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
+
+    /**
+     * 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 SCALE_X        = 0x0004;
+    private static final int SCALE_Y        = 0x0008;
+    private static final int ROTATION       = 0x0010;
+    private static final int ROTATION_X     = 0x0020;
+    private static final int ROTATION_Y     = 0x0040;
+    private static final int X              = 0x0080;
+    private static final int Y              = 0x0100;
+    private static final int ALPHA          = 0x0200;
+
+    private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y |
+            ROTATION | ROTATION_X | ROTATION_Y | X | Y;
+
+    /**
+     * The mechanism by which the user can request several properties that are then animated
+     * together works by posting this Runnable to start the underlying Animator. Every time
+     * a property animation is requested, we cancel any previous postings of the Runnable
+     * and re-post it. This means that we will only ever run the Runnable (and thus start the
+     * underlying animator) after the caller is done setting the properties that should be
+     * animated together.
+     */
+    private Runnable mAnimationStarter = new Runnable() {
+        @Override
+        public void run() {
+            startAnimation();
+        }
+    };
+
+    /**
+     * This class holds information about the overall animation being run on the set of
+     * properties. The mask describes which properties are being animated and the
+     * values holder is the list of all property/value objects.
+     */
+    private static class PropertyBundle {
+        int mPropertyMask;
+        ArrayList<NameValuesHolder> mNameValuesHolder;
+
+        PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
+            mPropertyMask = propertyMask;
+            mNameValuesHolder = nameValuesHolder;
+        }
+
+        /**
+         * Removes the given property from being animated as a part of this
+         * PropertyBundle. If the property was a part of this bundle, it returns
+         * true to indicate that it was, in fact, canceled. This is an indication
+         * to the caller that a cancellation actually occurred.
+         *
+         * @param propertyConstant The property whose cancellation is requested.
+         * @return true if the given property is a part of this bundle and if it
+         * has therefore been canceled.
+         */
+        boolean cancel(int propertyConstant) {
+            if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
+                int count = mNameValuesHolder.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
+                    if (nameValuesHolder.mNameConstant == propertyConstant) {
+                        mNameValuesHolder.remove(i);
+                        mPropertyMask &= ~propertyConstant;
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * This list tracks the list of properties being animated by any particular animator.
+     * In most situations, there would only ever be one animator running at a time. But it is
+     * possible to request some properties to animate together, then while those properties
+     * are animating, to request some other properties to animate together. The way that
+     * works is by having this map associate the group of properties being animated with the
+     * animator handling the animation. On every update event for an Animator, we ask the
+     * map for the associated properties and set them accordingly.
+     */
+    private HashMap<Animator, PropertyBundle> mAnimatorMap =
+            new HashMap<Animator, PropertyBundle>();
+
+    /**
+     * This is the information we need to set each property during the animation.
+     * mNameConstant is used to set the appropriate field in View, and the from/delta
+     * values are used to calculate the animated value for a given animation fraction
+     * during the animation.
+     */
+    private static class NameValuesHolder {
+        int mNameConstant;
+        float mFromValue;
+        float mDeltaValue;
+        NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
+            mNameConstant = nameConstant;
+            mFromValue = fromValue;
+            mDeltaValue = deltaValue;
+        }
+    }
+
+    /**
+     * Constructor, called by View. This is private by design, as the user should only
+     * get a ViewPropertyAnimator by calling View.animate().
+     *
+     * @param view The View associated with this ViewPropertyAnimator
+     */
+    ViewPropertyAnimatorHC(View view) {
+        mView = new WeakReference<View>(view);
+    }
+
+    /**
+     * Sets the duration for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param duration The length of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    duration);
+        }
+        mDurationSet = true;
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Returns the current duration of property animations. If the duration was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setDuration(long)
+     * @return The duration of animations, in milliseconds.
+     */
+    public long getDuration() {
+        if (mDurationSet) {
+            return mDuration;
+        } else {
+            // Just return the default from ValueAnimator, since that's what we'd get if
+            // the value has not been set otherwise
+            return new ValueAnimator().getDuration();
+        }
+    }
+
+    @Override
+    public long getStartDelay() {
+        if (mStartDelaySet) {
+            return mStartDelay;
+        } else {
+            // Just return the default from ValueAnimator (0), since that's what we'd get if
+            // the value has not been set otherwise
+            return 0;
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator setStartDelay(long startDelay) {
+        if (startDelay < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    startDelay);
+        }
+        mStartDelaySet = true;
+        mStartDelay = startDelay;
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator) {
+        mInterpolatorSet = true;
+        mInterpolator = interpolator;
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
+        mListener = listener;
+        return this;
+    }
+
+    @Override
+    public void start() {
+        startAnimation();
+    }
+
+    @Override
+    public void cancel() {
+        if (mAnimatorMap.size() > 0) {
+            HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+                    (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+            Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+            for (Animator runningAnim : animatorSet) {
+                runningAnim.cancel();
+            }
+        }
+        mPendingAnimations.clear();
+        View v = mView.get();
+        if (v != null) {
+            v.removeCallbacks(mAnimationStarter);
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator x(float value) {
+        animateProperty(X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator xBy(float value) {
+        animatePropertyBy(X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator y(float value) {
+        animateProperty(Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator yBy(float value) {
+        animatePropertyBy(Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotation(float value) {
+        animateProperty(ROTATION, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationBy(float value) {
+        animatePropertyBy(ROTATION, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationX(float value) {
+        animateProperty(ROTATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationXBy(float value) {
+        animatePropertyBy(ROTATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationY(float value) {
+        animateProperty(ROTATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationYBy(float value) {
+        animatePropertyBy(ROTATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationX(float value) {
+        animateProperty(TRANSLATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationXBy(float value) {
+        animatePropertyBy(TRANSLATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationY(float value) {
+        animateProperty(TRANSLATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationYBy(float value) {
+        animatePropertyBy(TRANSLATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleX(float value) {
+        animateProperty(SCALE_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleXBy(float value) {
+        animatePropertyBy(SCALE_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleY(float value) {
+        animateProperty(SCALE_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleYBy(float value) {
+        animatePropertyBy(SCALE_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alpha(float value) {
+        animateProperty(ALPHA, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alphaBy(float value) {
+        animatePropertyBy(ALPHA, value);
+        return this;
+    }
+
+    /**
+     * 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() {
+        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
+        ArrayList<NameValuesHolder> nameValueList =
+                (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
+        mPendingAnimations.clear();
+        int propertyMask = 0;
+        int propertyCount = nameValueList.size();
+        for (int i = 0; i < propertyCount; ++i) {
+            NameValuesHolder nameValuesHolder = nameValueList.get(i);
+            propertyMask |= nameValuesHolder.mNameConstant;
+        }
+        mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
+        animator.addUpdateListener(mAnimatorEventListener);
+        animator.addListener(mAnimatorEventListener);
+        if (mStartDelaySet) {
+            animator.setStartDelay(mStartDelay);
+        }
+        if (mDurationSet) {
+            animator.setDuration(mDuration);
+        }
+        if (mInterpolatorSet) {
+            animator.setInterpolator(mInterpolator);
+        }
+        animator.start();
+    }
+
+    /**
+     * Utility function, called by the various x(), y(), etc. methods. This stores the
+     * constant name for the property along with the from/delta values that will be used to
+     * calculate and set the property during the animation. This structure is added to the
+     * pending animations, awaiting the eventual start() of the underlying animator. A
+     * Runnable is posted to start the animation, and any pending such Runnable is canceled
+     * (which enables us to end up starting just one animator for all of the properties
+     * specified at one time).
+     *
+     * @param constantName The specifier for the property being animated
+     * @param toValue The value to which the property will animate
+     */
+    private void animateProperty(int constantName, float toValue) {
+        float fromValue = getValue(constantName);
+        float deltaValue = toValue - fromValue;
+        animatePropertyBy(constantName, fromValue, deltaValue);
+    }
+
+    /**
+     * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
+     * just like animateProperty(), except the value is an offset from the property's
+     * current value, instead of an absolute "to" value.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float byValue) {
+        float fromValue = getValue(constantName);
+        animatePropertyBy(constantName, fromValue, byValue);
+    }
+
+    /**
+     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
+     * details of adding a pending animation and posting the request to start the animation.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param startValue The starting value of the property
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float startValue, float byValue) {
+        // First, cancel any existing animations on this property
+        if (mAnimatorMap.size() > 0) {
+            Animator animatorToCancel = null;
+            Set<Animator> animatorSet = mAnimatorMap.keySet();
+            for (Animator runningAnim : animatorSet) {
+                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
+                if (bundle.cancel(constantName)) {
+                    // property was canceled - cancel the animation if it's now empty
+                    // Note that it's safe to break out here because every new animation
+                    // on a property will cancel a previous animation on that property, so
+                    // there can only ever be one such animation running.
+                    if (bundle.mPropertyMask == NONE) {
+                        // the animation is no longer changing anything - cancel it
+                        animatorToCancel = runningAnim;
+                        break;
+                    }
+                }
+            }
+            if (animatorToCancel != null) {
+                animatorToCancel.cancel();
+            }
+        }
+
+        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
+        mPendingAnimations.add(nameValuePair);
+        View v = mView.get();
+        if (v != null) {
+            v.removeCallbacks(mAnimationStarter);
+            v.post(mAnimationStarter);
+        }
+    }
+
+    /**
+     * This method handles setting the property values directly in the View object's fields.
+     * propertyConstant tells it which property should be set, value is the value to set
+     * the property to.
+     *
+     * @param propertyConstant The property to be set
+     * @param value The value to set the property to
+     */
+    private void setValue(int propertyConstant, float value) {
+        //final View.TransformationInfo info = mView.mTransformationInfo;
+        View v = mView.get();
+        if (v != null) {
+            switch (propertyConstant) {
+                case TRANSLATION_X:
+                    //info.mTranslationX = value;
+                    v.setTranslationX(value);
+                    break;
+                case TRANSLATION_Y:
+                    //info.mTranslationY = value;
+                    v.setTranslationY(value);
+                    break;
+                case ROTATION:
+                    //info.mRotation = value;
+                    v.setRotation(value);
+                    break;
+                case ROTATION_X:
+                    //info.mRotationX = value;
+                    v.setRotationX(value);
+                    break;
+                case ROTATION_Y:
+                    //info.mRotationY = value;
+                    v.setRotationY(value);
+                    break;
+                case SCALE_X:
+                    //info.mScaleX = value;
+                    v.setScaleX(value);
+                    break;
+                case SCALE_Y:
+                    //info.mScaleY = value;
+                    v.setScaleY(value);
+                    break;
+                case X:
+                    //info.mTranslationX = value - v.mLeft;
+                    v.setX(value);
+                    break;
+                case Y:
+                    //info.mTranslationY = value - v.mTop;
+                    v.setY(value);
+                    break;
+                case ALPHA:
+                    //info.mAlpha = value;
+                    v.setAlpha(value);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * This method gets the value of the named property from the View object.
+     *
+     * @param propertyConstant The property whose value should be returned
+     * @return float The value of the named property
+     */
+    private float getValue(int propertyConstant) {
+        //final View.TransformationInfo info = mView.mTransformationInfo;
+        View v = mView.get();
+        if (v != null) {
+            switch (propertyConstant) {
+                case TRANSLATION_X:
+                    //return info.mTranslationX;
+                    return v.getTranslationX();
+                case TRANSLATION_Y:
+                    //return info.mTranslationY;
+                    return v.getTranslationY();
+                case ROTATION:
+                    //return info.mRotation;
+                    return v.getRotation();
+                case ROTATION_X:
+                    //return info.mRotationX;
+                    return v.getRotationX();
+                case ROTATION_Y:
+                    //return info.mRotationY;
+                    return v.getRotationY();
+                case SCALE_X:
+                    //return info.mScaleX;
+                    return v.getScaleX();
+                case SCALE_Y:
+                    //return info.mScaleY;
+                    return v.getScaleY();
+                case X:
+                    //return v.mLeft + info.mTranslationX;
+                    return v.getX();
+                case Y:
+                    //return v.mTop + info.mTranslationY;
+                    return v.getY();
+                case ALPHA:
+                    //return info.mAlpha;
+                    return v.getAlpha();
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * Utility class that handles the various Animator events. The only ones we care
+     * about are the end event (which we use to clean up the animator map when an animator
+     * finishes) and the update event (which we use to calculate the current value of each
+     * property and then set it on the view object).
+     */
+    private class AnimatorEventListener
+            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationStart(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationCancel(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationRepeat(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationEnd(animation);
+            }
+            mAnimatorMap.remove(animation);
+            // If the map is empty, it means all animation are done or canceled, so the listener
+            // isn't needed anymore. Not nulling it would cause it to leak any objects used in
+            // its implementation
+            if (mAnimatorMap.isEmpty()) {
+                mListener = null;
+            }
+        }
+
+        /**
+         * Calculate the current value for each property and set it on the view. Invalidate
+         * the view object appropriately, depending on which properties are being animated.
+         *
+         * @param animation The animator associated with the properties that need to be
+         * set. This animator holds the animation fraction which we will use to calculate
+         * the current value of each property.
+         */
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            // alpha requires slightly different treatment than the other (transform) properties.
+            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+            // logic is dependent on how the view handles an internal call to onSetAlpha().
+            // We track what kinds of properties are set, and how alpha is handled when it is
+            // set, and perform the invalidation steps appropriately.
+            //boolean alphaHandled = false;
+            //mView.invalidateParentCaches();
+            float fraction = animation.getAnimatedFraction();
+            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
+            int propertyMask = propertyBundle.mPropertyMask;
+            if ((propertyMask & TRANSFORM_MASK) != 0) {
+                View v = mView.get();
+                if (v != null) {
+                    v.invalidate(/*false*/);
+                }
+            }
+            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
+            if (valueList != null) {
+                int count = valueList.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder values = valueList.get(i);
+                    float value = values.mFromValue + fraction * values.mDeltaValue;
+                    //if (values.mNameConstant == ALPHA) {
+                    //    alphaHandled = mView.setAlphaNoInvalidation(value);
+                    //} else {
+                        setValue(values.mNameConstant, value);
+                    //}
+                }
+            }
+            /*if ((propertyMask & TRANSFORM_MASK) != 0) {
+                mView.mTransformationInfo.mMatrixDirty = true;
+                mView.mPrivateFlags |= View.DRAWN; // force another invalidation
+            }*/
+            // invalidate(false) in all cases except if alphaHandled gets set to true
+            // via the call to setAlphaNoInvalidation(), above
+            View v = mView.get();
+            if (v != null) {
+                v.invalidate(/*alphaHandled*/);
+            }
+        }
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorICS.java
@@ -0,0 +1,298 @@
+package com.nineoldandroids.view;
+
+import java.lang.ref.WeakReference;
+
+import android.view.View;
+import android.view.animation.Interpolator;
+import com.nineoldandroids.animation.Animator.AnimatorListener;
+
+class ViewPropertyAnimatorICS extends ViewPropertyAnimator {
+    /**
+     * A value to be returned when the WeakReference holding the native implementation
+     * returns <code>null</code>
+     */
+    private final static long RETURN_WHEN_NULL = -1L;
+
+    /**
+     * A WeakReference holding the native implementation of ViewPropertyAnimator
+     */
+    private final WeakReference<android.view.ViewPropertyAnimator> mNative;
+
+    ViewPropertyAnimatorICS(View view) {
+        mNative = new WeakReference<android.view.ViewPropertyAnimator>(view.animate());
+    }
+
+    @Override
+    public ViewPropertyAnimator setDuration(long duration) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.setDuration(duration);
+        }
+        return this;
+    }
+
+    @Override
+    public long getDuration() {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            return n.getDuration();
+        }
+        return RETURN_WHEN_NULL;
+    }
+
+    @Override
+    public ViewPropertyAnimator setStartDelay(long startDelay) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.setStartDelay(startDelay);
+        }
+        return this;
+    }
+
+    @Override
+    public long getStartDelay() {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            return n.getStartDelay();
+        }
+        return RETURN_WHEN_NULL;
+    }
+
+    @Override
+    public ViewPropertyAnimator setInterpolator(Interpolator interpolator) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.setInterpolator(interpolator);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator setListener(final AnimatorListener listener) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            if (listener == null) {
+                n.setListener(null);
+            } else {
+                n.setListener(new android.animation.Animator.AnimatorListener() {
+                    @Override
+                    public void onAnimationStart(android.animation.Animator animation) {
+                        listener.onAnimationStart(null);
+                    }
+
+                    @Override
+                    public void onAnimationRepeat(android.animation.Animator animation) {
+                        listener.onAnimationRepeat(null);
+                    }
+
+                    @Override
+                    public void onAnimationEnd(android.animation.Animator animation) {
+                        listener.onAnimationEnd(null);
+                    }
+
+                    @Override
+                    public void onAnimationCancel(android.animation.Animator animation) {
+                        listener.onAnimationCancel(null);
+                    }
+                });
+            }
+        }
+        return this;
+    }
+
+    @Override
+    public void start() {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.start();
+        }
+    }
+
+    @Override
+    public void cancel() {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.cancel();
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator x(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.x(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator xBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.xBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator y(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.y(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator yBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.yBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotation(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotation(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotationBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationX(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotationX(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationXBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotationXBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationY(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotationY(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationYBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.rotationYBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationX(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.translationX(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationXBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.translationXBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationY(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.translationY(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationYBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.translationYBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleX(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.scaleX(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleXBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.scaleXBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleY(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.scaleY(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleYBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.scaleYBy(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alpha(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.alpha(value);
+        }
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alphaBy(float value) {
+        android.view.ViewPropertyAnimator n = mNative.get();
+        if (n != null) {
+            n.alphaBy(value);
+        }
+        return this;
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.nineoldandroids.view;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Set;
+import android.view.View;
+import android.view.animation.Interpolator;
+import com.nineoldandroids.animation.Animator;
+import com.nineoldandroids.animation.ValueAnimator;
+import com.nineoldandroids.view.animation.AnimatorProxy;
+
+class ViewPropertyAnimatorPreHC extends ViewPropertyAnimator {
+    /**
+     * Proxy animation class which will allow us access to post-Honeycomb properties that were not
+     * otherwise available.
+     */
+    private final AnimatorProxy mProxy;
+
+    /**
+     * A WeakReference holding the View whose properties are being animated by this class. This is
+     * set at construction time.
+     */
+    private final WeakReference<View> mView;
+
+    /**
+     * The duration of the underlying Animator object. By default, we don't set the duration
+     * on the Animator and just use its default duration. If the duration is ever set on this
+     * Animator, then we use the duration that it was set to.
+     */
+    private long mDuration;
+
+    /**
+     * A flag indicating whether the duration has been set on this object. If not, we don't set
+     * the duration on the underlying Animator, but instead just use its default duration.
+     */
+    private boolean mDurationSet = false;
+
+    /**
+     * The startDelay of the underlying Animator object. By default, we don't set the startDelay
+     * on the Animator and just use its default startDelay. If the startDelay is ever set on this
+     * Animator, then we use the startDelay that it was set to.
+     */
+    private long mStartDelay = 0;
+
+    /**
+     * A flag indicating whether the startDelay has been set on this object. If not, we don't set
+     * the startDelay on the underlying Animator, but instead just use its default startDelay.
+     */
+    private boolean mStartDelaySet = false;
+
+    /**
+     * The interpolator of the underlying Animator object. By default, we don't set the interpolator
+     * on the Animator and just use its default interpolator. If the interpolator is ever set on
+     * this Animator, then we use the interpolator that it was set to.
+     */
+    private /*Time*/Interpolator mInterpolator;
+
+    /**
+     * A flag indicating whether the interpolator has been set on this object. If not, we don't set
+     * the interpolator on the underlying Animator, but instead just use its default interpolator.
+     */
+    private boolean mInterpolatorSet = false;
+
+    /**
+     * Listener for the lifecycle events of the underlying
+     */
+    private Animator.AnimatorListener mListener = null;
+
+    /**
+     * 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.
+     */
+    private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener();
+
+    /**
+     * This list holds the properties that have been asked to animate. We allow the caller to
+     * request several animations prior to actually starting the underlying animator. This
+     * enables us to run one single animator to handle several properties in parallel. Each
+     * property is tossed onto the pending list until the animation actually starts (which is
+     * done by posting it onto mView), at which time the pending list is cleared and the properties
+     * on that list are added to the list of properties associated with that animator.
+     */
+    ArrayList<NameValuesHolder> mPendingAnimations = new ArrayList<NameValuesHolder>();
+
+    /**
+     * 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 SCALE_X        = 0x0004;
+    private static final int SCALE_Y        = 0x0008;
+    private static final int ROTATION       = 0x0010;
+    private static final int ROTATION_X     = 0x0020;
+    private static final int ROTATION_Y     = 0x0040;
+    private static final int X              = 0x0080;
+    private static final int Y              = 0x0100;
+    private static final int ALPHA          = 0x0200;
+
+    private static final int TRANSFORM_MASK = TRANSLATION_X | TRANSLATION_Y | SCALE_X | SCALE_Y |
+            ROTATION | ROTATION_X | ROTATION_Y | X | Y;
+
+    /**
+     * The mechanism by which the user can request several properties that are then animated
+     * together works by posting this Runnable to start the underlying Animator. Every time
+     * a property animation is requested, we cancel any previous postings of the Runnable
+     * and re-post it. This means that we will only ever run the Runnable (and thus start the
+     * underlying animator) after the caller is done setting the properties that should be
+     * animated together.
+     */
+    private Runnable mAnimationStarter = new Runnable() {
+        @Override
+        public void run() {
+            startAnimation();
+        }
+    };
+
+    /**
+     * This class holds information about the overall animation being run on the set of
+     * properties. The mask describes which properties are being animated and the
+     * values holder is the list of all property/value objects.
+     */
+    private static class PropertyBundle {
+        int mPropertyMask;
+        ArrayList<NameValuesHolder> mNameValuesHolder;
+
+        PropertyBundle(int propertyMask, ArrayList<NameValuesHolder> nameValuesHolder) {
+            mPropertyMask = propertyMask;
+            mNameValuesHolder = nameValuesHolder;
+        }
+
+        /**
+         * Removes the given property from being animated as a part of this
+         * PropertyBundle. If the property was a part of this bundle, it returns
+         * true to indicate that it was, in fact, canceled. This is an indication
+         * to the caller that a cancellation actually occurred.
+         *
+         * @param propertyConstant The property whose cancellation is requested.
+         * @return true if the given property is a part of this bundle and if it
+         * has therefore been canceled.
+         */
+        boolean cancel(int propertyConstant) {
+            if ((mPropertyMask & propertyConstant) != 0 && mNameValuesHolder != null) {
+                int count = mNameValuesHolder.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder nameValuesHolder = mNameValuesHolder.get(i);
+                    if (nameValuesHolder.mNameConstant == propertyConstant) {
+                        mNameValuesHolder.remove(i);
+                        mPropertyMask &= ~propertyConstant;
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * This list tracks the list of properties being animated by any particular animator.
+     * In most situations, there would only ever be one animator running at a time. But it is
+     * possible to request some properties to animate together, then while those properties
+     * are animating, to request some other properties to animate together. The way that
+     * works is by having this map associate the group of properties being animated with the
+     * animator handling the animation. On every update event for an Animator, we ask the
+     * map for the associated properties and set them accordingly.
+     */
+    private HashMap<Animator, PropertyBundle> mAnimatorMap =
+            new HashMap<Animator, PropertyBundle>();
+
+    /**
+     * This is the information we need to set each property during the animation.
+     * mNameConstant is used to set the appropriate field in View, and the from/delta
+     * values are used to calculate the animated value for a given animation fraction
+     * during the animation.
+     */
+    private static class NameValuesHolder {
+        int mNameConstant;
+        float mFromValue;
+        float mDeltaValue;
+        NameValuesHolder(int nameConstant, float fromValue, float deltaValue) {
+            mNameConstant = nameConstant;
+            mFromValue = fromValue;
+            mDeltaValue = deltaValue;
+        }
+    }
+
+    /**
+     * Constructor, called by View. This is private by design, as the user should only
+     * get a ViewPropertyAnimator by calling View.animate().
+     *
+     * @param view The View associated with this ViewPropertyAnimator
+     */
+    ViewPropertyAnimatorPreHC(View view) {
+        mView = new WeakReference<View>(view);
+        mProxy = AnimatorProxy.wrap(view);
+    }
+
+    /**
+     * Sets the duration for the underlying animator that animates the requested properties.
+     * By default, the animator uses the default value for ValueAnimator. Calling this method
+     * will cause the declared value to be used instead.
+     * @param duration The length of ensuing property animations, in milliseconds. The value
+     * cannot be negative.
+     * @return This object, allowing calls to methods in this class to be chained.
+     */
+    public ViewPropertyAnimator setDuration(long duration) {
+        if (duration < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    duration);
+        }
+        mDurationSet = true;
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Returns the current duration of property animations. If the duration was set on this
+     * object, that value is returned. Otherwise, the default value of the underlying Animator
+     * is returned.
+     *
+     * @see #setDuration(long)
+     * @return The duration of animations, in milliseconds.
+     */
+    public long getDuration() {
+        if (mDurationSet) {
+            return mDuration;
+        } else {
+            // Just return the default from ValueAnimator, since that's what we'd get if
+            // the value has not been set otherwise
+            return new ValueAnimator().getDuration();
+        }
+    }
+
+    @Override
+    public long getStartDelay() {
+        if (mStartDelaySet) {
+            return mStartDelay;
+        } else {
+            // Just return the default from ValueAnimator (0), since that's what we'd get if
+            // the value has not been set otherwise
+            return 0;
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator setStartDelay(long startDelay) {
+        if (startDelay < 0) {
+            throw new IllegalArgumentException("Animators cannot have negative duration: " +
+                    startDelay);
+        }
+        mStartDelaySet = true;
+        mStartDelay = startDelay;
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator setInterpolator(/*Time*/Interpolator interpolator) {
+        mInterpolatorSet = true;
+        mInterpolator = interpolator;
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator setListener(Animator.AnimatorListener listener) {
+        mListener = listener;
+        return this;
+    }
+
+    @Override
+    public void start() {
+        startAnimation();
+    }
+
+    @Override
+    public void cancel() {
+        if (mAnimatorMap.size() > 0) {
+            HashMap<Animator, PropertyBundle> mAnimatorMapCopy =
+                    (HashMap<Animator, PropertyBundle>)mAnimatorMap.clone();
+            Set<Animator> animatorSet = mAnimatorMapCopy.keySet();
+            for (Animator runningAnim : animatorSet) {
+                runningAnim.cancel();
+            }
+        }
+        mPendingAnimations.clear();
+        View v = mView.get();
+        if (v != null) {
+            v.removeCallbacks(mAnimationStarter);
+        }
+    }
+
+    @Override
+    public ViewPropertyAnimator x(float value) {
+        animateProperty(X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator xBy(float value) {
+        animatePropertyBy(X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator y(float value) {
+        animateProperty(Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator yBy(float value) {
+        animatePropertyBy(Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotation(float value) {
+        animateProperty(ROTATION, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationBy(float value) {
+        animatePropertyBy(ROTATION, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationX(float value) {
+        animateProperty(ROTATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationXBy(float value) {
+        animatePropertyBy(ROTATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationY(float value) {
+        animateProperty(ROTATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator rotationYBy(float value) {
+        animatePropertyBy(ROTATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationX(float value) {
+        animateProperty(TRANSLATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationXBy(float value) {
+        animatePropertyBy(TRANSLATION_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationY(float value) {
+        animateProperty(TRANSLATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator translationYBy(float value) {
+        animatePropertyBy(TRANSLATION_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleX(float value) {
+        animateProperty(SCALE_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleXBy(float value) {
+        animatePropertyBy(SCALE_X, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleY(float value) {
+        animateProperty(SCALE_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator scaleYBy(float value) {
+        animatePropertyBy(SCALE_Y, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alpha(float value) {
+        animateProperty(ALPHA, value);
+        return this;
+    }
+
+    @Override
+    public ViewPropertyAnimator alphaBy(float value) {
+        animatePropertyBy(ALPHA, value);
+        return this;
+    }
+
+    /**
+     * 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() {
+        ValueAnimator animator = ValueAnimator.ofFloat(1.0f);
+        ArrayList<NameValuesHolder> nameValueList =
+                (ArrayList<NameValuesHolder>) mPendingAnimations.clone();
+        mPendingAnimations.clear();
+        int propertyMask = 0;
+        int propertyCount = nameValueList.size();
+        for (int i = 0; i < propertyCount; ++i) {
+            NameValuesHolder nameValuesHolder = nameValueList.get(i);
+            propertyMask |= nameValuesHolder.mNameConstant;
+        }
+        mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList));
+        animator.addUpdateListener(mAnimatorEventListener);
+        animator.addListener(mAnimatorEventListener);
+        if (mStartDelaySet) {
+            animator.setStartDelay(mStartDelay);
+        }
+        if (mDurationSet) {
+            animator.setDuration(mDuration);
+        }
+        if (mInterpolatorSet) {
+            animator.setInterpolator(mInterpolator);
+        }
+        animator.start();
+    }
+
+    /**
+     * Utility function, called by the various x(), y(), etc. methods. This stores the
+     * constant name for the property along with the from/delta values that will be used to
+     * calculate and set the property during the animation. This structure is added to the
+     * pending animations, awaiting the eventual start() of the underlying animator. A
+     * Runnable is posted to start the animation, and any pending such Runnable is canceled
+     * (which enables us to end up starting just one animator for all of the properties
+     * specified at one time).
+     *
+     * @param constantName The specifier for the property being animated
+     * @param toValue The value to which the property will animate
+     */
+    private void animateProperty(int constantName, float toValue) {
+        float fromValue = getValue(constantName);
+        float deltaValue = toValue - fromValue;
+        animatePropertyBy(constantName, fromValue, deltaValue);
+    }
+
+    /**
+     * Utility function, called by the various xBy(), yBy(), etc. methods. This method is
+     * just like animateProperty(), except the value is an offset from the property's
+     * current value, instead of an absolute "to" value.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float byValue) {
+        float fromValue = getValue(constantName);
+        animatePropertyBy(constantName, fromValue, byValue);
+    }
+
+    /**
+     * Utility function, called by animateProperty() and animatePropertyBy(), which handles the
+     * details of adding a pending animation and posting the request to start the animation.
+     *
+     * @param constantName The specifier for the property being animated
+     * @param startValue The starting value of the property
+     * @param byValue The amount by which the property will change
+     */
+    private void animatePropertyBy(int constantName, float startValue, float byValue) {
+        // First, cancel any existing animations on this property
+        if (mAnimatorMap.size() > 0) {
+            Animator animatorToCancel = null;
+            Set<Animator> animatorSet = mAnimatorMap.keySet();
+            for (Animator runningAnim : animatorSet) {
+                PropertyBundle bundle = mAnimatorMap.get(runningAnim);
+                if (bundle.cancel(constantName)) {
+                    // property was canceled - cancel the animation if it's now empty
+                    // Note that it's safe to break out here because every new animation
+                    // on a property will cancel a previous animation on that property, so
+                    // there can only ever be one such animation running.
+                    if (bundle.mPropertyMask == NONE) {
+                        // the animation is no longer changing anything - cancel it
+                        animatorToCancel = runningAnim;
+                        break;
+                    }
+                }
+            }
+            if (animatorToCancel != null) {
+                animatorToCancel.cancel();
+            }
+        }
+
+        NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue);
+        mPendingAnimations.add(nameValuePair);
+        View v = mView.get();
+        if (v != null) {
+            v.removeCallbacks(mAnimationStarter);
+            v.post(mAnimationStarter);
+        }
+    }
+
+    /**
+     * This method handles setting the property values directly in the View object's fields.
+     * propertyConstant tells it which property should be set, value is the value to set
+     * the property to.
+     *
+     * @param propertyConstant The property to be set
+     * @param value The value to set the property to
+     */
+    private void setValue(int propertyConstant, float value) {
+        //final View.TransformationInfo info = mView.mTransformationInfo;
+        switch (propertyConstant) {
+            case TRANSLATION_X:
+                //info.mTranslationX = value;
+                mProxy.setTranslationX(value);
+                break;
+            case TRANSLATION_Y:
+                //info.mTranslationY = value;
+                mProxy.setTranslationY(value);
+                break;
+            case ROTATION:
+                //info.mRotation = value;
+                mProxy.setRotation(value);
+                break;
+            case ROTATION_X:
+                //info.mRotationX = value;
+                mProxy.setRotationX(value);
+                break;
+            case ROTATION_Y:
+                //info.mRotationY = value;
+                mProxy.setRotationY(value);
+                break;
+            case SCALE_X:
+                //info.mScaleX = value;
+                mProxy.setScaleX(value);
+                break;
+            case SCALE_Y:
+                //info.mScaleY = value;
+                mProxy.setScaleY(value);
+                break;
+            case X:
+                //info.mTranslationX = value - mView.mLeft;
+                mProxy.setX(value);
+                break;
+            case Y:
+                //info.mTranslationY = value - mView.mTop;
+                mProxy.setY(value);
+                break;
+            case ALPHA:
+                //info.mAlpha = value;
+                mProxy.setAlpha(value);
+                break;
+        }
+    }
+
+    /**
+     * This method gets the value of the named property from the View object.
+     *
+     * @param propertyConstant The property whose value should be returned
+     * @return float The value of the named property
+     */
+    private float getValue(int propertyConstant) {
+        //final View.TransformationInfo info = mView.mTransformationInfo;
+        switch (propertyConstant) {
+            case TRANSLATION_X:
+                //return info.mTranslationX;
+                return mProxy.getTranslationX();
+            case TRANSLATION_Y:
+                //return info.mTranslationY;
+                return mProxy.getTranslationY();
+            case ROTATION:
+                //return info.mRotation;
+                return mProxy.getRotation();
+            case ROTATION_X:
+                //return info.mRotationX;
+                return mProxy.getRotationX();
+            case ROTATION_Y:
+                //return info.mRotationY;
+                return mProxy.getRotationY();
+            case SCALE_X:
+                //return info.mScaleX;
+                return mProxy.getScaleX();
+            case SCALE_Y:
+                //return info.mScaleY;
+                return mProxy.getScaleY();
+            case X:
+                //return mView.mLeft + info.mTranslationX;
+                return mProxy.getX();
+            case Y:
+                //return mView.mTop + info.mTranslationY;
+                return mProxy.getY();
+            case ALPHA:
+                //return info.mAlpha;
+                return mProxy.getAlpha();
+        }
+        return 0;
+    }
+
+    /**
+     * Utility class that handles the various Animator events. The only ones we care
+     * about are the end event (which we use to clean up the animator map when an animator
+     * finishes) and the update event (which we use to calculate the current value of each
+     * property and then set it on the view object).
+     */
+    private class AnimatorEventListener
+            implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener {
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationStart(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationCancel(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationRepeat(animation);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mListener != null) {
+                mListener.onAnimationEnd(animation);
+            }
+            mAnimatorMap.remove(animation);
+            // If the map is empty, it means all animation are done or canceled, so the listener
+            // isn't needed anymore. Not nulling it would cause it to leak any objects used in
+            // its implementation
+            if (mAnimatorMap.isEmpty()) {
+                mListener = null;
+            }
+        }
+
+        /**
+         * Calculate the current value for each property and set it on the view. Invalidate
+         * the view object appropriately, depending on which properties are being animated.
+         *
+         * @param animation The animator associated with the properties that need to be
+         * set. This animator holds the animation fraction which we will use to calculate
+         * the current value of each property.
+         */
+        @Override
+        public void onAnimationUpdate(ValueAnimator animation) {
+            // alpha requires slightly different treatment than the other (transform) properties.
+            // The logic in setAlpha() is not simply setting mAlpha, plus the invalidation
+            // logic is dependent on how the view handles an internal call to onSetAlpha().
+            // We track what kinds of properties are set, and how alpha is handled when it is
+            // set, and perform the invalidation steps appropriately.
+            //boolean alphaHandled = false;
+            //mView.invalidateParentCaches();
+            float fraction = animation.getAnimatedFraction();
+            PropertyBundle propertyBundle = mAnimatorMap.get(animation);
+            int propertyMask = propertyBundle.mPropertyMask;
+            if ((propertyMask & TRANSFORM_MASK) != 0) {
+                View v = mView.get();
+                if (v != null) {
+                    v.invalidate(/*false*/);
+                }
+            }
+            ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder;
+            if (valueList != null) {
+                int count = valueList.size();
+                for (int i = 0; i < count; ++i) {
+                    NameValuesHolder values = valueList.get(i);
+                    float value = values.mFromValue + fraction * values.mDeltaValue;
+                    //if (values.mNameConstant == ALPHA) {
+                    //    alphaHandled = mView.setAlphaNoInvalidation(value);
+                    //} else {
+                        setValue(values.mNameConstant, value);
+                    //}
+                }
+            }
+            /*if ((propertyMask & TRANSFORM_MASK) != 0) {
+                mView.mTransformationInfo.mMatrixDirty = true;
+                mView.mPrivateFlags |= View.DRAWN; // force another invalidation
+            }*/
+            // invalidate(false) in all cases except if alphaHandled gets set to true
+            // via the call to setAlphaNoInvalidation(), above
+            View v = mView.get();
+            if (v != null) {
+                v.invalidate(/*alphaHandled*/);
+            }
+        }
+    }
+}
new file mode 100755
--- /dev/null
+++ b/mobile/android/thirdparty/com/nineoldandroids/view/animation/AnimatorProxy.java
@@ -0,0 +1,322 @@
+package com.nineoldandroids.view.animation;
+
+import android.graphics.Camera;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Build;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.Transformation;
+
+import java.lang.ref.WeakReference;
+import java.util.WeakHashMap;
+
+/**
+ * A proxy class to allow for modifying post-3.0 view properties on all pre-3.0
+ * platforms. <strong>DO NOT</strong> wrap your views with this class if you
+ * are using {@code ObjectAnimator} as it will handle that itself.
+ */
+public final class AnimatorProxy extends Animation {
+    /** Whether or not the current running platform needs to be proxied. */
+    public static final boolean NEEDS_PROXY = Integer.valueOf(Build.VERSION.SDK).intValue() < Build.VERSION_CODES.HONEYCOMB;
+
+    private static final WeakHashMap<View, AnimatorProxy> PROXIES =
+            new WeakHashMap<View, AnimatorProxy>();
+
+    /**
+     * Create a proxy to allow for modifying post-3.0 view properties on all
+     * pre-3.0 platforms. <strong>DO NOT</strong> wrap your views if you are
+     * using {@code ObjectAnimator} as it will handle that itself.
+     *
+     * @param view View to wrap.
+     * @return Proxy to post-3.0 properties.
+     */
+    public static AnimatorProxy wrap(View view) {
+        AnimatorProxy proxy = PROXIES.get(view);
+        // This checks if the proxy already exists and whether it still is the animation of the given view
+        if (proxy == null || proxy != view.getAnimation()) {
+            proxy = new AnimatorProxy(view);
+            PROXIES.put(view, proxy);
+        }
+        return proxy;
+    }
+
+    private final WeakReference<View> mView;
+    private final Camera mCamera = new Camera();
+    private boolean mHasPivot;
+
+    private float mAlpha = 1;
+    private float mPivotX;
+    private float mPivotY;
+    private float mRotationX;
+    private float mRotationY;
+    private float mRotationZ;
+    private float mScaleX = 1;
+    private float mScaleY = 1;
+    private float mTranslationX;
+    private float mTranslationY;
+
+    private final RectF mBefore = new RectF();
+    private final RectF mAfter = new RectF();
+    private final Matrix mTempMatrix = new Matrix();
+
+    private AnimatorProxy(View view) {
+        setDuration(0); //perform transformation immediately
+        setFillAfter(true); //persist transformation beyond duration
+        view.setAnimation(this);
+        mView = new WeakReference<View>(view);
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+    public void setAlpha(float alpha) {
+        if (mAlpha != alpha) {
+            mAlpha = alpha;
+            View view = mView.get();
+            if (view != null) {
+                view.invalidate();
+            }
+        }
+    }
+    public float getPivotX() {
+        return mPivotX;
+    }
+    public void setPivotX(float pivotX) {
+        if (!mHasPivot || mPivotX != pivotX) {
+            prepareForUpdate();
+            mHasPivot = true;
+            mPivotX = pivotX;
+            invalidateAfterUpdate();
+        }
+    }
+    public float getPivotY() {
+        return mPivotY;
+    }
+    public void setPivotY(float pivotY) {
+        if (!mHasPivot || mPivotY != pivotY) {
+            prepareForUpdate();
+            mHasPivot = true;
+            mPivotY = pivotY;
+            invalidateAfterUpdate();
+        }
+    }
+    public float getRotation() {
+        return mRotationZ;
+    }
+    public void setRotation(float rotation) {
+        if (mRotationZ != rotation) {
+            prepareForUpdate();
+            mRotationZ = rotation;
+            invalidateAfterUpdate();
+        }
+    }
+    public float getRotationX() {
+        return mRotationX;
+    }
+    public void setRotationX(float rotationX) {
+        if (mRotationX != rotationX) {
+            prepareForUpdate();
+            mRotationX = rotationX;
+            invalidateAfterUpdate();
+        }
+    }
+    public float getRotationY() {
+        return mRotationY;
+    }
+
+    public void setRotationY(float rotationY) {
+        if (mRotationY != rotationY) {
+            prepareForUpdate();
+            mRotationY = rotationY;
+            invalidateAfterUpdate();
+        }
+    }
+    public float getScaleX() {
+        return mScaleX;
+    }
+    public void setScaleX(float scaleX) {
+        if (mScaleX != scaleX) {