Support design - fix FloatingActionButton elevation

This bug has been fixed in the framework for api 25+ but
a between 21-24 still triggers a NPE when an animation has
a delay set (via "setDelay" or "after"). The current
workaround is introducing a no-op animation which mimics
the delay on the following one by having a duration.

Bug: 32136707
Test: ./gradlew support-design:connectedCheck --info \
      --daemon -Pandroid.testInstrumentationRunnerArg\
      uments.class=android.support.design.widget.Float\
      ingActionButtonTest

Change-Id: I75722d6e142c0d75170ef912b261c1d7a9b55a1e
diff --git a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
index d5fe912..506f459 100644
--- a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
+++ b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
@@ -16,7 +16,6 @@
 
 package android.support.design.widget;
 
-import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.StateListAnimator;
@@ -31,8 +30,6 @@
 import android.os.Build;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 class FloatingActionButtonLollipop extends FloatingActionButtonIcs {
@@ -102,14 +99,17 @@
 
         // Animate translationZ to 0 if not pressed
         set = new AnimatorSet();
-        // Use an AnimatorSet to set a start delay since there is a bug with ValueAnimator that
-        // prevents it from being cancelled properly when used with a StateListAnimator.
-        AnimatorSet anim = new AnimatorSet();
-        anim.play(ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f)
-                        .setDuration(PRESSED_ANIM_DURATION))
-                .after(PRESSED_ANIM_DURATION);
-        set.play(ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0))
-                .with(anim);
+        set.playSequentially(
+                ObjectAnimator.ofFloat(mView, "elevation", elevation).setDuration(0),
+                // This is a no-op animation which exists here only for introducing the duration
+                // because setting the delay (on the next animation) via "setDelay" or "after" can
+                // trigger a NPE between android versions 21 and 24 (due to a framework bug). The
+                // issue has been fixed in version 25.
+                ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, mView.getTranslationZ())
+                        .setDuration(PRESSED_ANIM_DELAY),
+                ObjectAnimator.ofFloat(mView, View.TRANSLATION_Z, 0f)
+                        .setDuration(PRESSED_ANIM_DURATION));
+
         set.setInterpolator(ANIM_INTERPOLATOR);
         stateListAnimator.addState(ENABLED_STATE_SET, set);
 
diff --git a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
index 59b0d48..2716165 100644
--- a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
+++ b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
@@ -103,6 +103,30 @@
         };
     }
 
+    public static ViewAction setCompatElevation(final float size) {
+        return new ViewAction() {
+            @Override
+            public Matcher<View> getConstraints() {
+                return isAssignableFrom(FloatingActionButton.class);
+            }
+
+            @Override
+            public String getDescription() {
+                return "Sets FloatingActionButton elevation";
+            }
+
+            @Override
+            public void perform(UiController uiController, View view) {
+                uiController.loopMainThreadUntilIdle();
+
+                final FloatingActionButton fab = (FloatingActionButton) view;
+                fab.setCompatElevation(size);
+
+                uiController.loopMainThreadUntilIdle();
+            }
+        };
+    }
+
     public static ViewAction setLayoutGravity(final int gravity) {
         return new ViewAction() {
             @Override
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
index 857121e..248b9df 100644
--- a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
@@ -18,10 +18,12 @@
 
 import static android.support.design.testutils.FloatingActionButtonActions.hideThenShow;
 import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintColor;
+import static android.support.design.testutils.FloatingActionButtonActions.setCompatElevation;
 import static android.support.design.testutils.FloatingActionButtonActions.setImageResource;
 import static android.support.design.testutils.FloatingActionButtonActions.setLayoutGravity;
 import static android.support.design.testutils.FloatingActionButtonActions.setSize;
 import static android.support.design.testutils.FloatingActionButtonActions.showThenHide;
+import static android.support.design.testutils.TestUtilsActions.setEnabled;
 import static android.support.design.testutils.TestUtilsMatchers.withFabBackgroundFill;
 import static android.support.design.testutils.TestUtilsMatchers.withFabContentAreaOnMargins;
 import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight;
@@ -137,4 +139,14 @@
                 .check(matches(not(isDisplayed())));
     }
 
+    @Test
+    public void testSetCompatElevation() {
+        onView(withId(R.id.fab_standard))
+                .perform(setEnabled(false))
+                .perform(setCompatElevation(0));
+
+        onView(withId(R.id.fab_standard))
+                .perform(setEnabled(true))
+                .perform(setCompatElevation(8));
+    }
 }