State based animators for Views

Set quantum theme buttons to elevate 2dp on press

Change-Id: Ibf4f5ef166b901382c304d392eba075836a96a35
diff --git a/api/current.txt b/api/current.txt
index 63a8421..e987b08 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1061,6 +1061,7 @@
     field public static final int startDelay = 16843746; // 0x10103e2
     field public static final int startOffset = 16843198; // 0x10101be
     field public static final deprecated int startYear = 16843132; // 0x101017c
+    field public static final int stateListAnimator = 16843860; // 0x1010454
     field public static final int stateNotNeeded = 16842774; // 0x1010016
     field public static final int state_above_anchor = 16842922; // 0x10100aa
     field public static final int state_accelerated = 16843547; // 0x101031b
@@ -2788,6 +2789,7 @@
   public class AnimatorInflater {
     ctor public AnimatorInflater();
     method public static android.animation.Animator loadAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException;
+    method public static android.animation.StateListAnimator loadStateListAnimator(android.content.Context, int) throws android.content.res.Resources.NotFoundException;
   }
 
   public abstract class AnimatorListenerAdapter implements android.animation.Animator.AnimatorListener android.animation.Animator.AnimatorPauseListener {
@@ -2984,6 +2986,12 @@
     method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
   }
 
+  public class StateListAnimator {
+    ctor public StateListAnimator();
+    method public void addState(int[], android.animation.Animator);
+    method public void jumpToCurrentState();
+  }
+
   public class TimeAnimator extends android.animation.ValueAnimator {
     ctor public TimeAnimator();
     method public void setTimeListener(android.animation.TimeAnimator.TimeListener);
@@ -30545,6 +30553,7 @@
     method public final int getScrollY();
     method public java.lang.String getSharedElementName();
     method public int getSolidColor();
+    method public android.animation.StateListAnimator getStateListAnimator();
     method protected int getSuggestedMinimumHeight();
     method protected int getSuggestedMinimumWidth();
     method public int getSystemUiVisibility();
@@ -30809,6 +30818,7 @@
     method public void setSelected(boolean);
     method public void setSharedElementName(java.lang.String);
     method public void setSoundEffectsEnabled(boolean);
+    method public void setStateListAnimator(android.animation.StateListAnimator);
     method public void setSystemUiVisibility(int);
     method public void setTag(java.lang.Object);
     method public void setTag(int, java.lang.Object);
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 20236aa..933135d 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -21,6 +21,7 @@
 import android.content.res.XmlResourceParser;
 import android.content.res.Resources.NotFoundException;
 import android.util.AttributeSet;
+import android.util.StateSet;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.animation.AnimationUtils;
@@ -87,9 +88,86 @@
         }
     }
 
+    public static StateListAnimator loadStateListAnimator(Context context, int id)
+            throws NotFoundException {
+        XmlResourceParser parser = null;
+        try {
+            parser = context.getResources().getAnimation(id);
+            return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+        } catch (XmlPullParserException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException(
+                            "Can't load state list animator resource ID #0x" +
+                                    Integer.toHexString(id)
+                    );
+            rnf.initCause(ex);
+            throw rnf;
+        } catch (IOException ex) {
+            Resources.NotFoundException rnf =
+                    new Resources.NotFoundException(
+                            "Can't load state list animator resource ID #0x" +
+                                    Integer.toHexString(id)
+                    );
+            rnf.initCause(ex);
+            throw rnf;
+        } finally {
+            if (parser != null) {
+                parser.close();
+            }
+        }
+    }
+
+    private static StateListAnimator createStateListAnimatorFromXml(Context context,
+            XmlPullParser parser, AttributeSet attributeSet)
+            throws IOException, XmlPullParserException {
+        int type;
+        StateListAnimator stateListAnimator = new StateListAnimator();
+
+        while (true) {
+            type = parser.next();
+            switch (type) {
+                case XmlPullParser.END_DOCUMENT:
+                case XmlPullParser.END_TAG:
+                    return stateListAnimator;
+
+                case XmlPullParser.START_TAG:
+                    // parse item
+                    Animator animator = null;
+                    if ("item".equals(parser.getName())) {
+                        int attributeCount = parser.getAttributeCount();
+                        int[] states = new int[attributeCount];
+                        int stateIndex = 0;
+                        for (int i = 0; i < attributeCount; i++) {
+                            int attrName = attributeSet.getAttributeNameResource(i);
+                            if (attrName == com.android.internal.R.attr.animation) {
+                                animator = loadAnimator(context,
+                                        attributeSet.getAttributeResourceValue(i, 0));
+                            } else {
+                                states[stateIndex++] =
+                                        attributeSet.getAttributeBooleanValue(i, false) ?
+                                                attrName : -attrName;
+                            }
+
+                        }
+                        if (animator == null) {
+                            animator = createAnimatorFromXml(context, parser);
+                        }
+
+                        if (animator == null) {
+                            throw new Resources.NotFoundException(
+                                    "animation state item must have a valid animation");
+                        }
+                        stateListAnimator
+                                .addState(StateSet.trimStateSet(states, stateIndex), animator);
+
+                    }
+                    break;
+            }
+        }
+    }
+
     private static Animator createAnimatorFromXml(Context c, XmlPullParser parser)
             throws XmlPullParserException, IOException {
-
         return createAnimatorFromXml(c, parser, Xml.asAttributeSet(parser), null, 0);
     }
 
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
new file mode 100644
index 0000000..bc4843d
--- /dev/null
+++ b/core/java/android/animation/StateListAnimator.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.animation;
+
+import android.util.StateSet;
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * Lets you define a number of Animators that will run on the attached View depending on the View's
+ * drawable state.
+ * <p>
+ * It can be defined in an XML file with the <code>&lt;selector></code> element.
+ * Each State Animator is defined in a nested <code>&lt;item></code> element.
+ *
+ * @attr ref android.R.styleable#DrawableStates_state_focused
+ * @attr ref android.R.styleable#DrawableStates_state_window_focused
+ * @attr ref android.R.styleable#DrawableStates_state_enabled
+ * @attr ref android.R.styleable#DrawableStates_state_checkable
+ * @attr ref android.R.styleable#DrawableStates_state_checked
+ * @attr ref android.R.styleable#DrawableStates_state_selected
+ * @attr ref android.R.styleable#DrawableStates_state_activated
+ * @attr ref android.R.styleable#DrawableStates_state_active
+ * @attr ref android.R.styleable#DrawableStates_state_single
+ * @attr ref android.R.styleable#DrawableStates_state_first
+ * @attr ref android.R.styleable#DrawableStates_state_middle
+ * @attr ref android.R.styleable#DrawableStates_state_last
+ * @attr ref android.R.styleable#DrawableStates_state_pressed
+ * @attr ref android.R.styleable#StateListAnimatorItem_animation
+ */
+public class StateListAnimator {
+
+    private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+
+    private Tuple mLastMatch = null;
+
+    private Animator mRunningAnimator = null;
+
+    private WeakReference<View> mViewRef;
+
+    private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (mRunningAnimator == animation) {
+                mRunningAnimator = null;
+            }
+        }
+    };
+
+    /**
+     * Associates the given animator with the provided drawable state specs so that it will be run
+     * when the View's drawable state matches the specs.
+     *
+     * @param specs The drawable state specs to match against
+     * @param animator The animator to run when the specs match
+     */
+    public void addState(int[] specs, Animator animator) {
+        Tuple tuple = new Tuple(specs, animator);
+        tuple.mAnimator.addListener(mAnimatorListener);
+        mTuples.add(tuple);
+    }
+
+    /**
+     * Returns the current {@link android.animation.Animator} which is started because of a state
+     * change.
+     *
+     * @return The currently running Animator or null if no Animator is running
+     * @hide
+     */
+    public Animator getRunningAnimator() {
+        return mRunningAnimator;
+    }
+
+    /**
+     * @hide
+     */
+    public View getTarget() {
+        return mViewRef == null ? null : mViewRef.get();
+    }
+
+    /**
+     * Called by View
+     * @hide
+     */
+    public void setTarget(View view) {
+        final View current = getTarget();
+        if (current == view) {
+            return;
+        }
+        if (current != null) {
+            clearTarget();
+        }
+        if (view != null) {
+            mViewRef = new WeakReference<View>(view);
+        }
+
+    }
+
+    private void clearTarget() {
+        final int size = mTuples.size();
+        for (int i = 0; i < size; i++) {
+            mTuples.get(i).mAnimator.setTarget(null);
+        }
+
+        mViewRef = null;
+        mLastMatch = null;
+        mRunningAnimator = null;
+    }
+
+    /**
+     * Called by View
+     * @hide
+     */
+    public void setState(int[] state) {
+        Tuple match = null;
+        final int count = mTuples.size();
+        for (int i = 0; i < count; i++) {
+            final Tuple tuple = mTuples.get(i);
+            if (StateSet.stateSetMatches(tuple.mSpecs, state)) {
+                match = tuple;
+                break;
+            }
+        }
+        if (match == mLastMatch) {
+            return;
+        }
+        if (mLastMatch != null) {
+            cancel(mLastMatch);
+        }
+        mLastMatch = match;
+        if (match != null) {
+            start(match);
+        }
+    }
+
+    private void start(Tuple match) {
+        match.mAnimator.setTarget(getTarget());
+        mRunningAnimator = match.mAnimator;
+        match.mAnimator.start();
+    }
+
+    private void cancel(Tuple lastMatch) {
+        lastMatch.mAnimator.cancel();
+        lastMatch.mAnimator.setTarget(null);
+    }
+
+    /**
+     * @hide
+     */
+    public ArrayList<Tuple> getTuples() {
+        return mTuples;
+    }
+
+    /**
+     * If there is an animation running for a recent state change, ends it.
+     * <p>
+     * This causes the animation to assign the end value(s) to the View.
+     */
+    public void jumpToCurrentState() {
+        if (mRunningAnimator != null) {
+            mRunningAnimator.end();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static class Tuple {
+
+        final int[] mSpecs;
+
+        final Animator mAnimator;
+
+        private Tuple(int[] specs, Animator animator) {
+            mSpecs = specs;
+            mAnimator = animator;
+        }
+
+        /**
+         * @hide
+         */
+        public int[] getSpecs() {
+            return mSpecs;
+        }
+
+        /**
+         * @hide
+         */
+        public Animator getAnimator() {
+            return mAnimator;
+        }
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d8fcfc5..bef96b1 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,7 +16,9 @@
 
 package android.view;
 
+import android.animation.AnimatorInflater;
 import android.animation.RevealAnimator;
+import android.animation.StateListAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -667,6 +669,7 @@
  * @attr ref android.R.styleable#View_scrollbarTrackVertical
  * @attr ref android.R.styleable#View_scrollbarAlwaysDrawHorizontalTrack
  * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack
+ * @attr ref android.R.styleable#View_stateListAnimator
  * @attr ref android.R.styleable#View_sharedElementName
  * @attr ref android.R.styleable#View_soundEffectsEnabled
  * @attr ref android.R.styleable#View_tag
@@ -3258,6 +3261,11 @@
     private Outline mOutline;
 
     /**
+     * Animator that automatically runs based on state changes.
+     */
+    private StateListAnimator mStateListAnimator;
+
+    /**
      * When this view has focus and the next focus is {@link #FOCUS_LEFT},
      * the user may specify which view to go to next.
      */
@@ -3995,6 +4003,10 @@
                 case R.styleable.View_nestedScrollingEnabled:
                     setNestedScrollingEnabled(a.getBoolean(attr, false));
                     break;
+                case R.styleable.View_stateListAnimator:
+                    setStateListAnimator(AnimatorInflater.loadStateListAnimator(context,
+                            a.getResourceId(attr, 0)));
+                    break;
             }
         }
 
@@ -10620,6 +10632,40 @@
     }
 
     /**
+     * Returns the current StateListAnimator if exists.
+     *
+     * @return StateListAnimator or null if it does not exists
+     * @see    #setStateListAnimator(android.animation.StateListAnimator)
+     */
+    public StateListAnimator getStateListAnimator() {
+        return mStateListAnimator;
+    }
+
+    /**
+     * Attaches the provided StateListAnimator to this View.
+     * <p>
+     * Any previously attached StateListAnimator will be detached.
+     *
+     * @param stateListAnimator The StateListAnimator to update the view
+     * @see {@link android.animation.StateListAnimator}
+     */
+    public void setStateListAnimator(StateListAnimator stateListAnimator) {
+        if (mStateListAnimator == stateListAnimator) {
+            return;
+        }
+        if (mStateListAnimator != null) {
+            mStateListAnimator.setTarget(null);
+        }
+        mStateListAnimator = stateListAnimator;
+        if (stateListAnimator != null) {
+            stateListAnimator.setTarget(this);
+            if (isAttachedToWindow()) {
+                stateListAnimator.setState(getDrawableState());
+            }
+        }
+    }
+
+    /**
      * Sets the outline of the view, which defines the shape of the shadow it
      * casts.
      * <p>
@@ -12835,7 +12881,6 @@
         destroyLayer(false);
 
         cleanupDraw();
-
         mCurrentAnimation = null;
     }
 
@@ -15489,9 +15534,11 @@
     /**
      * This function is called whenever the state of the view changes in such
      * a way that it impacts the state of drawables being shown.
-     *
-     * <p>Be sure to call through to the superclass when overriding this
-     * function.
+     * <p>
+     * If the View has a StateListAnimator, it will also be called to run necessary state
+     * change animations.
+     * <p>
+     * Be sure to call through to the superclass when overriding this function.
      *
      * @see Drawable#setState(int[])
      */
@@ -15500,6 +15547,10 @@
         if (d != null && d.isStateful()) {
             d.setState(getDrawableState());
         }
+
+        if (mStateListAnimator != null) {
+            mStateListAnimator.setState(getDrawableState());
+        }
     }
 
     /**
@@ -15644,11 +15695,17 @@
     /**
      * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}
      * on all Drawable objects associated with this view.
+     * <p>
+     * Also calls {@link StateListAnimator#jumpToCurrentState()} if there is a StateListAnimator
+     * attached to this view.
      */
     public void jumpDrawablesToCurrentState() {
         if (mBackground != null) {
             mBackground.jumpToCurrentState();
         }
+        if (mStateListAnimator != null) {
+            mStateListAnimator.jumpToCurrentState();
+        }
     }
 
     /**
diff --git a/core/res/res/anim/button_state_list_anim_quantum.xml b/core/res/res/anim/button_state_list_anim_quantum.xml
new file mode 100644
index 0000000..01989a4
--- /dev/null
+++ b/core/res/res/anim/button_state_list_anim_quantum.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:state_enabled="true">
+        <set>
+            <objectAnimator android:propertyName="translationZ"
+                            android:duration="@integer/button_pressed_animation_duration"
+                            android:valueTo="@dimen/button_pressed_z"
+                            android:valueType="floatType"/>
+        </set>
+    </item>
+    <!-- base state -->
+    <item>
+        <set>
+            <objectAnimator android:propertyName="translationZ"
+                            android:duration="@integer/button_pressed_animation_duration"
+                            android:valueTo="0"
+                            android:valueType="floatType"/>
+        </set>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0326e18..172877f 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2364,6 +2364,9 @@
         <!-- Specifies that this view should permit nested scrolling within a compatible
              ancestor view. -->
         <attr name="nestedScrollingEnabled" format="boolean" />
+
+        <!-- Sets the state-based animator for the View. -->
+        <attr name="stateListAnimator" format="reference"/>
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -4255,6 +4258,11 @@
         <attr name="drawable" format="reference" />
     </declare-styleable>
 
+    <!-- Attributes that can be assigned to a StateListAnimator item. -->
+    <declare-styleable name="StateListAnimatorItem">
+        <attr name="animation"/>
+    </declare-styleable>
+
     <!-- Drawable used to render a geometric shape, with a gradient or a solid color. -->
     <declare-styleable name="GradientDrawable">
         <!-- Indicates whether the drawable should intially be visible. -->
diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index cebee12..53e97fd 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -49,4 +49,7 @@
 
     <dimen name="floating_window_z">16dp</dimen>
     <dimen name="floating_window_margin">32dp</dimen>
+
+    <!-- the amount of elevation for pressed button state-->
+    <dimen name="button_pressed_z">2dp</dimen>
 </resources>
diff --git a/core/res/res/values/integers.xml b/core/res/res/values/integers.xml
index dc90bbf..e6748c4 100644
--- a/core/res/res/values/integers.xml
+++ b/core/res/res/values/integers.xml
@@ -19,4 +19,5 @@
 <resources>
     <integer name="kg_carousel_angle">75</integer>
     <integer name="kg_glowpad_rotation_offset">0</integer>
+    <integer name="button_pressed_animation_duration">100</integer>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 8874c30..d0cdefe 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2171,6 +2171,7 @@
   <public type="attr" name="actionOverflowMenuStyle" />
   <public type="attr" name="documentLaunchMode" />
   <public type="attr" name="autoRemoveFromRecents" />
+  <public type="attr" name="stateListAnimator" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 01c3017..88a2a9f 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -364,6 +364,7 @@
         <item name="textColor">?attr/textColorPrimary</item>
         <item name="minHeight">48dip</item>
         <item name="minWidth">96dip</item>
+        <item name="stateListAnimator">@anim/button_state_list_anim_quantum</item>
     </style>
 
     <!-- Small bordered ink button -->
@@ -375,6 +376,7 @@
     <!-- Borderless ink button -->
     <style name="Widget.Quantum.Button.Borderless">
         <item name="background">@drawable/btn_borderless_quantum</item>
+        <item name="stateListAnimator">@null</item>
     </style>
 
     <!-- Small borderless ink button -->
diff --git a/core/tests/coretests/res/anim/reset_state_anim.xml b/core/tests/coretests/res/anim/reset_state_anim.xml
new file mode 100644
index 0000000..918d0a3
--- /dev/null
+++ b/core/tests/coretests/res/anim/reset_state_anim.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+</set>
\ No newline at end of file
diff --git a/core/tests/coretests/res/anim/test_state_anim.xml b/core/tests/coretests/res/anim/test_state_anim.xml
new file mode 100644
index 0000000..9e08f68
--- /dev/null
+++ b/core/tests/coretests/res/anim/test_state_anim.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <set>
+            <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="20" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="20" android:valueType="floatType"/>
+        </set>
+    </item>
+    <item android:state_enabled="true" android:state_pressed="false">
+        <set>
+            <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+        </set>
+    </item>
+    <!-- base state-->
+    <item android:animation="@anim/reset_state_anim"/>
+</selector>
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
new file mode 100644
index 0000000..38df78d
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
@@ -0,0 +1,102 @@
+/*
+* Copyright (C) 2014 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+
+package android.animation;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.util.StateSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.frameworks.coretests.R;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
+
+    public StateListAnimatorTest() {
+        super(BasicAnimatorActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public void testInflateFromAnimator() throws Exception {
+        StateListAnimator stateListAnimator = AnimatorInflater
+                .loadStateListAnimator(getActivity(), R.anim.test_state_anim);
+        assertNotNull("A state list animator should be returned", stateListAnimator);
+        assertEquals("State list animator should have three items", 3,
+                stateListAnimator.getTuples().size());
+    }
+
+    @UiThreadTest
+    public void testAttachDetach() throws Exception {
+        View view = new View(getActivity());
+        final AtomicInteger setStateCount = new AtomicInteger(0);
+        StateListAnimator stateListAnimator = new StateListAnimator() {
+            @Override
+            public void setState(int[] state) {
+                setStateCount.incrementAndGet();
+                super.setState(state);
+            }
+        };
+        view.setStateListAnimator(stateListAnimator);
+        assertNotNull("State list animator should have a reference to view even if it is detached",
+                stateListAnimator.getTarget());
+        ViewGroup viewGroup = (ViewGroup) getActivity().findViewById(android.R.id.content);
+        int preSetStateCount = setStateCount.get();
+        viewGroup.addView(view);
+        assertTrue("When view is attached, state list drawable's setState should be called",
+                preSetStateCount < setStateCount.get());
+
+        StateListAnimator stateListAnimator2 = new StateListAnimator();
+        view.setStateListAnimator(stateListAnimator2);
+        assertNull("When a new state list animator is assigned, previous one should be detached",
+                stateListAnimator.getTarget());
+        assertNull("Any running animator should be removed on detach",
+                stateListAnimator.getRunningAnimator());
+        assertEquals("The new state list animator should be attached to the view",
+                view, stateListAnimator2.getTarget());
+        viewGroup.removeView(view);
+        assertNotNull("When view is detached from window, state list animator should still keep the"
+                        + " reference",
+                stateListAnimator2.getTarget());
+    }
+
+    public void testStateListLoading() throws InterruptedException {
+        StateListAnimator stateListAnimator = AnimatorInflater
+                .loadStateListAnimator(getActivity(), R.anim.test_state_anim);
+        assertNotNull("A state list animator should be returned", stateListAnimator);
+        assertEquals("Steate list animator should have two items", 3,
+                stateListAnimator.getTuples().size());
+        StateListAnimator.Tuple tuple1 = stateListAnimator.getTuples().get(0);
+        assertEquals("first tuple should have one state", 1, tuple1.getSpecs().length);
+        assertEquals("first spec in tuple 1 should be pressed",
+                com.android.internal.R.attr.state_pressed, tuple1.getSpecs()[0]);
+
+        StateListAnimator.Tuple tuple2 = stateListAnimator.getTuples().get(1);
+        assertEquals("Second tuple should have two specs", 2, tuple2.getSpecs().length);
+        assertTrue("Tuple two should match the expected state",
+                StateSet.stateSetMatches(tuple2.getSpecs(),
+                new int[]{-com.android.internal.R.attr.state_pressed,
+                com.android.internal.R.attr.state_enabled}));
+    }
+}