Merge "Move the set up of uploadArchives to SupportLibraryPlugin"
diff --git a/compat/gingerbread/android/support/v4/animation/AnimatorListenerCompat.java b/compat/gingerbread/android/support/v4/animation/AnimatorListenerCompat.java
deleted file mode 100644
index a2c043f..0000000
--- a/compat/gingerbread/android/support/v4/animation/AnimatorListenerCompat.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * <p>An animation listener that receives notifications from an animation.
- * Notifications indicate animation related events, such as the end or the
- * repetition of the animation.</p>
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public interface AnimatorListenerCompat {
-
-    /**
-     * <p>Notifies the start of the animation.</p>
-     *
-     * @param animation The started animation.
-     */
-    void onAnimationStart(ValueAnimatorCompat 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(ValueAnimatorCompat 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(ValueAnimatorCompat animation);
-
-    /**
-     * <p>Notifies the repetition of the animation.</p>
-     *
-     * @param animation The animation which was repeated.
-     */
-    void onAnimationRepeat(ValueAnimatorCompat animation);
-}
diff --git a/compat/gingerbread/android/support/v4/animation/AnimatorProvider.java b/compat/gingerbread/android/support/v4/animation/AnimatorProvider.java
deleted file mode 100644
index 51d4bb0..0000000
--- a/compat/gingerbread/android/support/v4/animation/AnimatorProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import android.view.View;
-
-/**
- * A simple interface to do things in animation pulse.
- * <p>
- * Before Honeycomb, it uses a simple Handler to mimic animation callback.
- * <p>
- * This is only a minimal implementation which is why this class is hidden.
- */
-interface AnimatorProvider {
-
-    /**
-     * Provides a simple ValueAnimator w/o any start or end values. It provides the same
-     * Animator callback interface.
-     */
-    ValueAnimatorCompat emptyValueAnimator();
-
-    void clearInterpolator(View view);
-}
diff --git a/compat/gingerbread/android/support/v4/animation/AnimatorUpdateListenerCompat.java b/compat/gingerbread/android/support/v4/animation/AnimatorUpdateListenerCompat.java
deleted file mode 100644
index 2cf3fbd..0000000
--- a/compat/gingerbread/android/support/v4/animation/AnimatorUpdateListenerCompat.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
-
-/**
- * Implementors of this interface can add themselves as update listeners
- * to a <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>.
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public interface AnimatorUpdateListenerCompat {
-
-    /**
-     * <p>Notifies the occurrence of another frame of the animation.</p>
-     *
-     * @param animation The animation which was repeated.
-     */
-    void onAnimationUpdate(ValueAnimatorCompat animation);
-
-}
\ No newline at end of file
diff --git a/compat/gingerbread/android/support/v4/animation/GingerbreadAnimatorCompatProvider.java b/compat/gingerbread/android/support/v4/animation/GingerbreadAnimatorCompatProvider.java
deleted file mode 100644
index ec5379c..0000000
--- a/compat/gingerbread/android/support/v4/animation/GingerbreadAnimatorCompatProvider.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import android.support.annotation.RequiresApi;
-import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Provides similar functionality to Animators on platforms prior to Honeycomb.
- * <p>
- * This is not a fully implemented API which is why it is not public.
- */
-
-@RequiresApi(9)
-class GingerbreadAnimatorCompatProvider implements AnimatorProvider {
-
-    @Override
-    public ValueAnimatorCompat emptyValueAnimator() {
-        return new GingerbreadFloatValueAnimator();
-    }
-
-    private static class GingerbreadFloatValueAnimator implements ValueAnimatorCompat {
-
-        List<AnimatorListenerCompat> mListeners = new ArrayList<AnimatorListenerCompat>();
-        List<AnimatorUpdateListenerCompat> mUpdateListeners
-                = new ArrayList<AnimatorUpdateListenerCompat>();
-        View mTarget;
-        private long mStartTime;
-        private long mDuration = 200;
-        private float mFraction = 0f;
-
-        private boolean mStarted = false;
-        private boolean mEnded = false;
-
-        public GingerbreadFloatValueAnimator() {
-        }
-
-        private Runnable mLoopRunnable = new Runnable() {
-            @Override
-            public void run() {
-                long dt = getTime() - mStartTime;
-                float fraction = dt * 1f / mDuration;
-                if (fraction > 1f || mTarget.getParent() == null) {
-                    fraction = 1f;
-                }
-                mFraction = fraction;
-                notifyUpdateListeners();
-                if (mFraction >= 1f) {
-                    dispatchEnd();
-                } else {
-                    mTarget.postDelayed(mLoopRunnable, 16);
-                }
-            }
-        };
-
-        private void notifyUpdateListeners() {
-            for (int i = mUpdateListeners.size() - 1; i >= 0; i--) {
-                mUpdateListeners.get(i).onAnimationUpdate(this);
-            }
-        }
-
-        @Override
-        public void setTarget(View view) {
-            mTarget = view;
-        }
-
-        @Override
-        public void addListener(AnimatorListenerCompat listener) {
-            mListeners.add(listener);
-        }
-
-        @Override
-        public void setDuration(long duration) {
-            if (!mStarted) {
-                mDuration = duration;
-            }
-        }
-
-        @Override
-        public void start() {
-            if (mStarted) {
-                return;
-            }
-            mStarted = true;
-            dispatchStart();
-            mFraction = 0f;
-            mStartTime = getTime();
-            mTarget.postDelayed(mLoopRunnable, 16);
-        }
-
-        private long getTime() {
-            return mTarget.getDrawingTime();
-        }
-
-        private void dispatchStart() {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onAnimationStart(this);
-            }
-        }
-
-        private void dispatchEnd() {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onAnimationEnd(this);
-            }
-        }
-
-        private void dispatchCancel() {
-            for (int i = mListeners.size() - 1; i >= 0; i--) {
-                mListeners.get(i).onAnimationCancel(this);
-            }
-        }
-
-        @Override
-        public void cancel() {
-            if (mEnded) {
-                return;
-            }
-            mEnded = true;
-            if (mStarted) {
-                dispatchCancel();
-            }
-            dispatchEnd();
-        }
-
-        @Override
-        public void addUpdateListener(AnimatorUpdateListenerCompat animatorUpdateListener) {
-            mUpdateListeners.add(animatorUpdateListener);
-        }
-
-        @Override
-        public float getAnimatedFraction() {
-            return mFraction;
-        }
-    }
-
-    @Override
-    public void clearInterpolator(View view) {
-    }
-}
diff --git a/compat/gingerbread/android/support/v4/animation/ValueAnimatorCompat.java b/compat/gingerbread/android/support/v4/animation/ValueAnimatorCompat.java
deleted file mode 100644
index b064030..0000000
--- a/compat/gingerbread/android/support/v4/animation/ValueAnimatorCompat.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.support.annotation.RestrictTo;
-import android.view.View;
-
-/**
- * Compatibility implementation for {@code android.animation.ValueAnimator}.
- *
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public interface ValueAnimatorCompat {
-
-    public void setTarget(View view);
-
-    public void addListener(AnimatorListenerCompat listener);
-
-    public void setDuration(long duration);
-
-    public void start();
-
-    public void cancel();
-
-    void addUpdateListener(AnimatorUpdateListenerCompat animatorUpdateListener);
-
-    public float getAnimatedFraction();
-}
diff --git a/compat/honeycomb_mr1/android/support/v4/animation/HoneycombMr1AnimatorCompatProvider.java b/compat/honeycomb_mr1/android/support/v4/animation/HoneycombMr1AnimatorCompatProvider.java
deleted file mode 100644
index 8f5a670..0000000
--- a/compat/honeycomb_mr1/android/support/v4/animation/HoneycombMr1AnimatorCompatProvider.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import android.animation.Animator;
-import android.animation.TimeInterpolator;
-import android.animation.ValueAnimator;
-import android.support.annotation.RequiresApi;
-import android.view.View;
-
-/**
- * Uses framework Animators to provide ValueAnimatorCompat interface.
- * <p/>
- * This is not a fully implemented API which is why it is not public.
- */
-
-@RequiresApi(12)
-class HoneycombMr1AnimatorCompatProvider implements AnimatorProvider {
-
-    private TimeInterpolator mDefaultInterpolator;
-
-    @Override
-    public ValueAnimatorCompat emptyValueAnimator() {
-        return new HoneycombValueAnimatorCompat(ValueAnimator.ofFloat(0f, 1f));
-    }
-
-    static class HoneycombValueAnimatorCompat implements ValueAnimatorCompat {
-
-        final Animator mWrapped;
-
-        public HoneycombValueAnimatorCompat(Animator wrapped) {
-            mWrapped = wrapped;
-        }
-
-        @Override
-        public void setTarget(View view) {
-            mWrapped.setTarget(view);
-        }
-
-        @Override
-        public void addListener(AnimatorListenerCompat listener) {
-            mWrapped.addListener(new AnimatorListenerCompatWrapper(listener, this));
-        }
-
-        @Override
-        public void setDuration(long duration) {
-            mWrapped.setDuration(duration);
-        }
-
-        @Override
-        public void start() {
-            mWrapped.start();
-        }
-
-        @Override
-        public void cancel() {
-            mWrapped.cancel();
-        }
-
-        @Override
-        public void addUpdateListener(final AnimatorUpdateListenerCompat animatorUpdateListener) {
-            if (mWrapped instanceof ValueAnimator) {
-                ((ValueAnimator) mWrapped).addUpdateListener(
-                        new ValueAnimator.AnimatorUpdateListener() {
-                            @Override
-                            public void onAnimationUpdate(ValueAnimator animation) {
-                                animatorUpdateListener
-                                        .onAnimationUpdate(HoneycombValueAnimatorCompat.this);
-                            }
-                        });
-            }
-        }
-
-        @Override
-        public float getAnimatedFraction() {
-            return ((ValueAnimator) mWrapped).getAnimatedFraction();
-        }
-    }
-
-    static class AnimatorListenerCompatWrapper implements Animator.AnimatorListener {
-
-        final AnimatorListenerCompat mWrapped;
-
-        final ValueAnimatorCompat mValueAnimatorCompat;
-
-        public AnimatorListenerCompatWrapper(
-                AnimatorListenerCompat wrapped, ValueAnimatorCompat valueAnimatorCompat) {
-            mWrapped = wrapped;
-            mValueAnimatorCompat = valueAnimatorCompat;
-        }
-
-        @Override
-        public void onAnimationStart(Animator animation) {
-            mWrapped.onAnimationStart(mValueAnimatorCompat);
-        }
-
-        @Override
-        public void onAnimationEnd(Animator animation) {
-            mWrapped.onAnimationEnd(mValueAnimatorCompat);
-        }
-
-        @Override
-        public void onAnimationCancel(Animator animation) {
-            mWrapped.onAnimationCancel(mValueAnimatorCompat);
-        }
-
-        @Override
-        public void onAnimationRepeat(Animator animation) {
-            mWrapped.onAnimationRepeat(mValueAnimatorCompat);
-        }
-    }
-
-    @Override
-    public void clearInterpolator(View view) {
-        if (mDefaultInterpolator == null) {
-            mDefaultInterpolator = new ValueAnimator().getInterpolator();
-        }
-        view.animate().setInterpolator(mDefaultInterpolator);
-    }
-}
diff --git a/compat/java/android/support/v4/animation/AnimatorCompatHelper.java b/compat/java/android/support/v4/animation/AnimatorCompatHelper.java
deleted file mode 100644
index 46b5b15..0000000
--- a/compat/java/android/support/v4/animation/AnimatorCompatHelper.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.v4.animation;
-
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
-import android.os.Build;
-import android.support.annotation.RestrictTo;
-import android.view.View;
-
-/**
- * @hide
- */
-@RestrictTo(LIBRARY_GROUP)
-public final class AnimatorCompatHelper {
-
-    private final static AnimatorProvider IMPL;
-
-    static {
-        if (Build.VERSION.SDK_INT >= 12) {
-            IMPL = new HoneycombMr1AnimatorCompatProvider();
-        } else {
-            IMPL = new GingerbreadAnimatorCompatProvider();
-        }
-    }
-
-    public static ValueAnimatorCompat emptyValueAnimator() {
-        return IMPL.emptyValueAnimator();
-    }
-
-    private AnimatorCompatHelper() {}
-
-    public static void clearInterpolator(View view) {
-        IMPL.clearInterpolator(view);
-    }
-}
diff --git a/design/base/android/support/design/internal/BottomNavigationAnimationHelperBase.java b/design/base/android/support/design/internal/BottomNavigationAnimationHelperBase.java
deleted file mode 100644
index 22501c1..0000000
--- a/design/base/android/support/design/internal/BottomNavigationAnimationHelperBase.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2016 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.support.design.internal;
-
-import android.view.ViewGroup;
-
-class BottomNavigationAnimationHelperBase {
-    void beginDelayedTransition(ViewGroup view) {
-        // Do nothing.
-    }
-}
diff --git a/design/base/android/support/design/widget/FloatingActionButtonImpl.java b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
index 36ccbf2..132cd81 100644
--- a/design/base/android/support/design/widget/FloatingActionButtonImpl.java
+++ b/design/base/android/support/design/widget/FloatingActionButtonImpl.java
@@ -16,6 +16,9 @@
 
 package android.support.design.widget;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
@@ -23,15 +26,21 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.annotation.RequiresApi;
 import android.support.design.R;
 import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.ViewCompat;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
 
-abstract class FloatingActionButtonImpl {
-
+@RequiresApi(14)
+class FloatingActionButtonImpl {
     static final Interpolator ANIM_INTERPOLATOR = AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR;
     static final long PRESSED_ANIM_DURATION = 100;
     static final long PRESSED_ANIM_DELAY = 100;
@@ -42,6 +51,12 @@
 
     int mAnimState = ANIM_STATE_NONE;
 
+    private final StateListAnimator mStateListAnimator;
+
+    ShadowDrawableWrapper mShadowDrawable;
+
+    private float mRotation;
+
     Drawable mShapeDrawable;
     Drawable mRippleDrawable;
     CircularBorderDrawable mBorderDrawable;
@@ -51,8 +66,8 @@
     float mPressedTranslationZ;
 
     interface InternalVisibilityChangedListener {
-        public void onShown();
-        public void onHidden();
+        void onShown();
+        void onHidden();
     }
 
     static final int SHOW_HIDE_ANIM_DURATION = 200;
@@ -66,26 +81,92 @@
 
     final VisibilityAwareImageButton mView;
     final ShadowViewDelegate mShadowViewDelegate;
-    final ValueAnimatorCompat.Creator mAnimatorCreator;
 
     private final Rect mTmpRect = new Rect();
     private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
 
     FloatingActionButtonImpl(VisibilityAwareImageButton view,
-            ShadowViewDelegate shadowViewDelegate, ValueAnimatorCompat.Creator animatorCreator) {
+            ShadowViewDelegate shadowViewDelegate) {
         mView = view;
         mShadowViewDelegate = shadowViewDelegate;
-        mAnimatorCreator = animatorCreator;
+
+        mStateListAnimator = new StateListAnimator();
+
+        // Elevate with translationZ when pressed or focused
+        mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET,
+                createAnimator(new ElevateToTranslationZAnimation()));
+        mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET,
+                createAnimator(new ElevateToTranslationZAnimation()));
+        // Reset back to elevation by default
+        mStateListAnimator.addState(ENABLED_STATE_SET,
+                createAnimator(new ResetElevationAnimation()));
+        // Set to 0 when disabled
+        mStateListAnimator.addState(EMPTY_STATE_SET,
+                createAnimator(new DisabledElevationAnimation()));
+
+        mRotation = mView.getRotation();
     }
 
-    abstract void setBackgroundDrawable(ColorStateList backgroundTint,
-            PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth);
+    void setBackgroundDrawable(ColorStateList backgroundTint,
+            PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) {
+        // Now we need to tint the original background with the tint, using
+        // an InsetDrawable if we have a border width
+        mShapeDrawable = DrawableCompat.wrap(createShapeDrawable());
+        DrawableCompat.setTintList(mShapeDrawable, backgroundTint);
+        if (backgroundTintMode != null) {
+            DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode);
+        }
 
-    abstract void setBackgroundTintList(ColorStateList tint);
+        // Now we created a mask Drawable which will be used for touch feedback.
+        GradientDrawable touchFeedbackShape = createShapeDrawable();
 
-    abstract void setBackgroundTintMode(PorterDuff.Mode tintMode);
+        // We'll now wrap that touch feedback mask drawable with a ColorStateList. We do not need
+        // to inset for any border here as LayerDrawable will nest the padding for us
+        mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape);
+        DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
 
-    abstract void setRippleColor(int rippleColor);
+        final Drawable[] layers;
+        if (borderWidth > 0) {
+            mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint);
+            layers = new Drawable[] {mBorderDrawable, mShapeDrawable, mRippleDrawable};
+        } else {
+            mBorderDrawable = null;
+            layers = new Drawable[] {mShapeDrawable, mRippleDrawable};
+        }
+
+        mContentBackground = new LayerDrawable(layers);
+
+        mShadowDrawable = new ShadowDrawableWrapper(
+                mView.getContext(),
+                mContentBackground,
+                mShadowViewDelegate.getRadius(),
+                mElevation,
+                mElevation + mPressedTranslationZ);
+        mShadowDrawable.setAddPaddingForCorners(false);
+        mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable);
+    }
+
+    void setBackgroundTintList(ColorStateList tint) {
+        if (mShapeDrawable != null) {
+            DrawableCompat.setTintList(mShapeDrawable, tint);
+        }
+        if (mBorderDrawable != null) {
+            mBorderDrawable.setBorderTint(tint);
+        }
+    }
+
+    void setBackgroundTintMode(PorterDuff.Mode tintMode) {
+        if (mShapeDrawable != null) {
+            DrawableCompat.setTintMode(mShapeDrawable, tintMode);
+        }
+    }
+
+
+    void setRippleColor(int rippleColor) {
+        if (mRippleDrawable != null) {
+            DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
+        }
+    }
 
     final void setElevation(float elevation) {
         if (mElevation != elevation) {
@@ -94,7 +175,9 @@
         }
     }
 
-    abstract float getElevation();
+    float getElevation() {
+        return mElevation;
+    }
 
     final void setPressedTranslationZ(float translationZ) {
         if (mPressedTranslationZ != translationZ) {
@@ -103,21 +186,130 @@
         }
     }
 
-    abstract void onElevationsChanged(float elevation, float pressedTranslationZ);
+    void onElevationsChanged(float elevation, float pressedTranslationZ) {
+        if (mShadowDrawable != null) {
+            mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ);
+            updatePadding();
+        }
+    }
 
-    abstract void onDrawableStateChanged(int[] state);
+    void onDrawableStateChanged(int[] state) {
+        mStateListAnimator.setState(state);
+    }
 
-    abstract void jumpDrawableToCurrentState();
+    void jumpDrawableToCurrentState() {
+        mStateListAnimator.jumpToCurrentState();
+    }
 
-    abstract void hide(@Nullable InternalVisibilityChangedListener listener, boolean fromUser);
+    void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
+        if (isOrWillBeHidden()) {
+            // We either are or will soon be hidden, skip the call
+            return;
+        }
 
-    abstract void show(@Nullable InternalVisibilityChangedListener listener, boolean fromUser);
+        mView.animate().cancel();
+
+        if (shouldAnimateVisibilityChange()) {
+            mAnimState = ANIM_STATE_HIDING;
+
+            mView.animate()
+                    .scaleX(0f)
+                    .scaleY(0f)
+                    .alpha(0f)
+                    .setDuration(SHOW_HIDE_ANIM_DURATION)
+                    .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
+                    .setListener(new AnimatorListenerAdapter() {
+                        private boolean mCancelled;
+
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            mView.internalSetVisibility(View.VISIBLE, fromUser);
+                            mCancelled = false;
+                        }
+
+                        @Override
+                        public void onAnimationCancel(Animator animation) {
+                            mCancelled = true;
+                        }
+
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mAnimState = ANIM_STATE_NONE;
+
+                            if (!mCancelled) {
+                                mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE,
+                                        fromUser);
+                                if (listener != null) {
+                                    listener.onHidden();
+                                }
+                            }
+                        }
+                    });
+        } else {
+            // If the view isn't laid out, or we're in the editor, don't run the animation
+            mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE, fromUser);
+            if (listener != null) {
+                listener.onHidden();
+            }
+        }
+    }
+
+    void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
+        if (isOrWillBeShown()) {
+            // We either are or will soon be visible, skip the call
+            return;
+        }
+
+        mView.animate().cancel();
+
+        if (shouldAnimateVisibilityChange()) {
+            mAnimState = ANIM_STATE_SHOWING;
+
+            if (mView.getVisibility() != View.VISIBLE) {
+                // If the view isn't visible currently, we'll animate it from a single pixel
+                mView.setAlpha(0f);
+                mView.setScaleY(0f);
+                mView.setScaleX(0f);
+            }
+
+            mView.animate()
+                    .scaleX(1f)
+                    .scaleY(1f)
+                    .alpha(1f)
+                    .setDuration(SHOW_HIDE_ANIM_DURATION)
+                    .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR)
+                    .setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationStart(Animator animation) {
+                            mView.internalSetVisibility(View.VISIBLE, fromUser);
+                        }
+
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mAnimState = ANIM_STATE_NONE;
+                            if (listener != null) {
+                                listener.onShown();
+                            }
+                        }
+                    });
+        } else {
+            mView.internalSetVisibility(View.VISIBLE, fromUser);
+            mView.setAlpha(1f);
+            mView.setScaleY(1f);
+            mView.setScaleX(1f);
+            if (listener != null) {
+                listener.onShown();
+            }
+        }
+    }
 
     final Drawable getContentBackground() {
         return mContentBackground;
     }
 
-    abstract void onCompatShadowChanged();
+    void onCompatShadowChanged() {
+        // Ignore pre-v21
+    }
 
     final void updatePadding() {
         Rect rect = mTmpRect;
@@ -126,7 +318,9 @@
         mShadowViewDelegate.setShadowPadding(rect.left, rect.top, rect.right, rect.bottom);
     }
 
-    abstract void getPadding(Rect rect);
+    void getPadding(Rect rect) {
+        mShadowDrawable.getPadding(rect);
+    }
 
     void onPaddingUpdated(Rect padding) {}
 
@@ -145,7 +339,7 @@
     }
 
     boolean requirePreDrawListener() {
-        return false;
+        return true;
     }
 
     CircularBorderDrawable createBorderDrawable(int borderWidth, ColorStateList backgroundTint) {
@@ -166,6 +360,11 @@
     }
 
     void onPreDraw() {
+        final float rotation = mView.getRotation();
+        if (mRotation != rotation) {
+            mRotation = rotation;
+            updateFromViewRotation();
+        }
     }
 
     private void ensurePreDrawListener() {
@@ -210,4 +409,123 @@
             return mAnimState != ANIM_STATE_SHOWING;
         }
     }
+
+    private ValueAnimator createAnimator(@NonNull ShadowAnimatorImpl impl) {
+        final ValueAnimator animator = new ValueAnimator();
+        animator.setInterpolator(ANIM_INTERPOLATOR);
+        animator.setDuration(PRESSED_ANIM_DURATION);
+        animator.addListener(impl);
+        animator.addUpdateListener(impl);
+        animator.setFloatValues(0, 1);
+        return animator;
+    }
+
+    private abstract class ShadowAnimatorImpl extends AnimatorListenerAdapter
+            implements ValueAnimator.AnimatorUpdateListener {
+        private boolean mValidValues;
+        private float mShadowSizeStart;
+        private float mShadowSizeEnd;
+
+        @Override
+        public void onAnimationUpdate(ValueAnimator animator) {
+            if (!mValidValues) {
+                mShadowSizeStart = mShadowDrawable.getShadowSize();
+                mShadowSizeEnd = getTargetShadowSize();
+                mValidValues = true;
+            }
+
+            mShadowDrawable.setShadowSize(mShadowSizeStart
+                    + ((mShadowSizeEnd - mShadowSizeStart) * animator.getAnimatedFraction()));
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mShadowDrawable.setShadowSize(mShadowSizeEnd);
+            mValidValues = false;
+        }
+
+        /**
+         * @return the shadow size we want to animate to.
+         */
+        protected abstract float getTargetShadowSize();
+    }
+
+    private class ResetElevationAnimation extends ShadowAnimatorImpl {
+        ResetElevationAnimation() {
+        }
+
+        @Override
+        protected float getTargetShadowSize() {
+            return mElevation;
+        }
+    }
+
+    private class ElevateToTranslationZAnimation extends ShadowAnimatorImpl {
+        ElevateToTranslationZAnimation() {
+        }
+
+        @Override
+        protected float getTargetShadowSize() {
+            return mElevation + mPressedTranslationZ;
+        }
+    }
+
+    private class DisabledElevationAnimation extends ShadowAnimatorImpl {
+        DisabledElevationAnimation() {
+        }
+
+        @Override
+        protected float getTargetShadowSize() {
+            return 0f;
+        }
+    }
+
+    private static ColorStateList createColorStateList(int selectedColor) {
+        final int[][] states = new int[3][];
+        final int[] colors = new int[3];
+        int i = 0;
+
+        states[i] = FOCUSED_ENABLED_STATE_SET;
+        colors[i] = selectedColor;
+        i++;
+
+        states[i] = PRESSED_ENABLED_STATE_SET;
+        colors[i] = selectedColor;
+        i++;
+
+        // Default enabled state
+        states[i] = new int[0];
+        colors[i] = Color.TRANSPARENT;
+        i++;
+
+        return new ColorStateList(states, colors);
+    }
+
+    private boolean shouldAnimateVisibilityChange() {
+        return ViewCompat.isLaidOut(mView) && !mView.isInEditMode();
+    }
+
+    private void updateFromViewRotation() {
+        if (Build.VERSION.SDK_INT == 19) {
+            // KitKat seems to have an issue with views which are rotated with angles which are
+            // not divisible by 90. Worked around by moving to software rendering in these cases.
+            if ((mRotation % 90) != 0) {
+                if (mView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
+                    mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+                }
+            } else {
+                if (mView.getLayerType() != View.LAYER_TYPE_NONE) {
+                    mView.setLayerType(View.LAYER_TYPE_NONE, null);
+                }
+            }
+        }
+
+        // Offset any View rotation
+        if (mShadowDrawable != null) {
+            mShadowDrawable.setRotation(-mRotation);
+        }
+        if (mBorderDrawable != null) {
+            mBorderDrawable.setRotation(-mRotation);
+        }
+    }
 }
diff --git a/design/base/android/support/design/widget/StateListAnimator.java b/design/base/android/support/design/widget/StateListAnimator.java
index 4378ef9..aef24be 100644
--- a/design/base/android/support/design/widget/StateListAnimator.java
+++ b/design/base/android/support/design/widget/StateListAnimator.java
@@ -16,6 +16,9 @@
 
 package android.support.design.widget;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.util.StateSet;
 
 import java.util.ArrayList;
@@ -25,17 +28,17 @@
     private final ArrayList<Tuple> mTuples = new ArrayList<>();
 
     private Tuple mLastMatch = null;
-    ValueAnimatorCompat mRunningAnimator = null;
+    ValueAnimator mRunningAnimator = null;
 
-    private final ValueAnimatorCompat.AnimatorListener mAnimationListener
-            = new ValueAnimatorCompat.AnimatorListenerAdapter() {
-        @Override
-        public void onAnimationEnd(ValueAnimatorCompat animator) {
-            if (mRunningAnimator == animator) {
-                mRunningAnimator = null;
-            }
-        }
-    };
+    private final ValueAnimator.AnimatorListener mAnimationListener =
+            new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animator) {
+                    if (mRunningAnimator == animator) {
+                        mRunningAnimator = null;
+                    }
+                }
+            };
 
     /**
      * Associates the given Animation with the provided drawable state specs so that it will be run
@@ -44,7 +47,7 @@
      * @param specs    The drawable state specs to match against
      * @param animator The animator to run when the specs match
      */
-    public void addState(int[] specs, ValueAnimatorCompat animator) {
+    public void addState(int[] specs, ValueAnimator animator) {
         Tuple tuple = new Tuple(specs, animator);
         animator.addListener(mAnimationListener);
         mTuples.add(tuple);
@@ -103,9 +106,9 @@
 
     static class Tuple {
         final int[] mSpecs;
-        final ValueAnimatorCompat mAnimator;
+        final ValueAnimator mAnimator;
 
-        Tuple(int[] specs, ValueAnimatorCompat animator) {
+        Tuple(int[] specs, ValueAnimator animator) {
             mSpecs = specs;
             mAnimator = animator;
         }
diff --git a/design/base/android/support/design/widget/ValueAnimatorCompat.java b/design/base/android/support/design/widget/ValueAnimatorCompat.java
deleted file mode 100644
index 6c52db3..0000000
--- a/design/base/android/support/design/widget/ValueAnimatorCompat.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.support.annotation.NonNull;
-import android.view.animation.Interpolator;
-
-/**
- * This class offers a very small subset of {@code ValueAnimator}'s API, but works pre-v11 too.
- * <p>
- * You shouldn't not instantiate this directly. Instead use {@code ViewUtils.createAnimator()}.
- */
-class ValueAnimatorCompat {
-
-    interface AnimatorUpdateListener {
-        /**
-         * <p>Notifies the occurrence of another frame of the animation.</p>
-         *
-         * @param animator The animation which was repeated.
-         */
-        void onAnimationUpdate(ValueAnimatorCompat animator);
-    }
-
-    /**
-     * An animation listener receives notifications from an animation.
-     * Notifications indicate animation related events, such as the end or the
-     * repetition of the animation.
-     */
-    interface AnimatorListener {
-        /**
-         * <p>Notifies the start of the animation.</p>
-         *
-         * @param animator The started animation.
-         */
-        void onAnimationStart(ValueAnimatorCompat animator);
-        /**
-         * <p>Notifies the end of the animation. This callback is not invoked
-         * for animations with repeat count set to INFINITE.</p>
-         *
-         * @param animator The animation which reached its end.
-         */
-        void onAnimationEnd(ValueAnimatorCompat animator);
-        /**
-         * <p>Notifies the cancellation of the animation. This callback is not invoked
-         * for animations with repeat count set to INFINITE.</p>
-         *
-         * @param animator The animation which was canceled.
-         */
-        void onAnimationCancel(ValueAnimatorCompat animator);
-    }
-
-    static class AnimatorListenerAdapter implements AnimatorListener {
-        @Override
-        public void onAnimationStart(ValueAnimatorCompat animator) {
-        }
-
-        @Override
-        public void onAnimationEnd(ValueAnimatorCompat animator) {
-        }
-
-        @Override
-        public void onAnimationCancel(ValueAnimatorCompat animator) {
-        }
-    }
-
-    interface Creator {
-        @NonNull
-        ValueAnimatorCompat createAnimator();
-    }
-
-    static abstract class Impl {
-        interface AnimatorUpdateListenerProxy {
-            void onAnimationUpdate();
-        }
-
-        interface AnimatorListenerProxy {
-            void onAnimationStart();
-            void onAnimationEnd();
-            void onAnimationCancel();
-        }
-
-        abstract void start();
-        abstract boolean isRunning();
-        abstract void setInterpolator(Interpolator interpolator);
-        abstract void addListener(AnimatorListenerProxy listener);
-        abstract void addUpdateListener(AnimatorUpdateListenerProxy updateListener);
-        abstract void setIntValues(int from, int to);
-        abstract int getAnimatedIntValue();
-        abstract void setFloatValues(float from, float to);
-        abstract float getAnimatedFloatValue();
-        abstract void setDuration(long duration);
-        abstract void cancel();
-        abstract float getAnimatedFraction();
-        abstract void end();
-        abstract long getDuration();
-    }
-
-    private final Impl mImpl;
-
-    ValueAnimatorCompat(Impl impl) {
-        mImpl = impl;
-    }
-
-    public void start() {
-        mImpl.start();
-    }
-
-    public boolean isRunning() {
-        return mImpl.isRunning();
-    }
-
-    public void setInterpolator(Interpolator interpolator) {
-        mImpl.setInterpolator(interpolator);
-    }
-
-    public void addUpdateListener(final AnimatorUpdateListener updateListener) {
-        if (updateListener != null) {
-            mImpl.addUpdateListener(new Impl.AnimatorUpdateListenerProxy() {
-                @Override
-                public void onAnimationUpdate() {
-                    updateListener.onAnimationUpdate(ValueAnimatorCompat.this);
-                }
-            });
-        } else {
-            mImpl.addUpdateListener(null);
-        }
-    }
-
-    public void addListener(final AnimatorListener listener) {
-        if (listener != null) {
-            mImpl.addListener(new Impl.AnimatorListenerProxy() {
-                @Override
-                public void onAnimationStart() {
-                    listener.onAnimationStart(ValueAnimatorCompat.this);
-                }
-
-                @Override
-                public void onAnimationEnd() {
-                    listener.onAnimationEnd(ValueAnimatorCompat.this);
-                }
-
-                @Override
-                public void onAnimationCancel() {
-                    listener.onAnimationCancel(ValueAnimatorCompat.this);
-                }
-            });
-        } else {
-            mImpl.addListener(null);
-        }
-    }
-
-    public void setIntValues(int from, int to) {
-        mImpl.setIntValues(from, to);
-    }
-
-    public int getAnimatedIntValue() {
-        return mImpl.getAnimatedIntValue();
-    }
-
-    public void setFloatValues(float from, float to) {
-        mImpl.setFloatValues(from, to);
-    }
-
-    public float getAnimatedFloatValue() {
-        return mImpl.getAnimatedFloatValue();
-    }
-
-    public void setDuration(long duration) {
-        mImpl.setDuration(duration);
-    }
-
-    public void cancel() {
-        mImpl.cancel();
-    }
-
-    public float getAnimatedFraction() {
-        return mImpl.getAnimatedFraction();
-    }
-
-    public void end() {
-        mImpl.end();
-    }
-
-    public long getDuration() {
-        return mImpl.getDuration();
-    }
-}
diff --git a/design/build.gradle b/design/build.gradle
index eec83bd..be28dc5 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -39,8 +39,6 @@
         main.java.srcDirs = [
                 'base',
                 'gingerbread',
-                'honeycomb',
-                'honeycomb-mr1',
                 'ics',
                 'lollipop',
                 'src'
diff --git a/design/gingerbread/android/support/design/widget/FloatingActionButtonGingerbread.java b/design/gingerbread/android/support/design/widget/FloatingActionButtonGingerbread.java
deleted file mode 100644
index 6edc9e4..0000000
--- a/design/gingerbread/android/support/design/widget/FloatingActionButtonGingerbread.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.PorterDuff;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.LayerDrawable;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.design.R;
-import android.support.design.widget.AnimationUtils.AnimationListenerAdapter;
-import android.support.v4.graphics.drawable.DrawableCompat;
-import android.view.View;
-import android.view.animation.Animation;
-
-class FloatingActionButtonGingerbread extends FloatingActionButtonImpl {
-
-    private final StateListAnimator mStateListAnimator;
-
-    ShadowDrawableWrapper mShadowDrawable;
-
-    FloatingActionButtonGingerbread(VisibilityAwareImageButton view,
-            ShadowViewDelegate shadowViewDelegate, ValueAnimatorCompat.Creator animatorCreator) {
-        super(view, shadowViewDelegate, animatorCreator);
-
-        mStateListAnimator = new StateListAnimator();
-
-        // Elevate with translationZ when pressed or focused
-        mStateListAnimator.addState(PRESSED_ENABLED_STATE_SET,
-                createAnimator(new ElevateToTranslationZAnimation()));
-        mStateListAnimator.addState(FOCUSED_ENABLED_STATE_SET,
-                createAnimator(new ElevateToTranslationZAnimation()));
-        // Reset back to elevation by default
-        mStateListAnimator.addState(ENABLED_STATE_SET,
-                createAnimator(new ResetElevationAnimation()));
-        // Set to 0 when disabled
-        mStateListAnimator.addState(EMPTY_STATE_SET,
-                createAnimator(new DisabledElevationAnimation()));
-    }
-
-    @Override
-    void setBackgroundDrawable(ColorStateList backgroundTint,
-            PorterDuff.Mode backgroundTintMode, int rippleColor, int borderWidth) {
-        // Now we need to tint the original background with the tint, using
-        // an InsetDrawable if we have a border width
-        mShapeDrawable = DrawableCompat.wrap(createShapeDrawable());
-        DrawableCompat.setTintList(mShapeDrawable, backgroundTint);
-        if (backgroundTintMode != null) {
-            DrawableCompat.setTintMode(mShapeDrawable, backgroundTintMode);
-        }
-
-        // Now we created a mask Drawable which will be used for touch feedback.
-        GradientDrawable touchFeedbackShape = createShapeDrawable();
-
-        // We'll now wrap that touch feedback mask drawable with a ColorStateList. We do not need
-        // to inset for any border here as LayerDrawable will nest the padding for us
-        mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape);
-        DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
-
-        final Drawable[] layers;
-        if (borderWidth > 0) {
-            mBorderDrawable = createBorderDrawable(borderWidth, backgroundTint);
-            layers = new Drawable[] {mBorderDrawable, mShapeDrawable, mRippleDrawable};
-        } else {
-            mBorderDrawable = null;
-            layers = new Drawable[] {mShapeDrawable, mRippleDrawable};
-        }
-
-        mContentBackground = new LayerDrawable(layers);
-
-        mShadowDrawable = new ShadowDrawableWrapper(
-                mView.getContext(),
-                mContentBackground,
-                mShadowViewDelegate.getRadius(),
-                mElevation,
-                mElevation + mPressedTranslationZ);
-        mShadowDrawable.setAddPaddingForCorners(false);
-        mShadowViewDelegate.setBackgroundDrawable(mShadowDrawable);
-    }
-
-    @Override
-    void setBackgroundTintList(ColorStateList tint) {
-        if (mShapeDrawable != null) {
-            DrawableCompat.setTintList(mShapeDrawable, tint);
-        }
-        if (mBorderDrawable != null) {
-            mBorderDrawable.setBorderTint(tint);
-        }
-    }
-
-    @Override
-    void setBackgroundTintMode(PorterDuff.Mode tintMode) {
-        if (mShapeDrawable != null) {
-            DrawableCompat.setTintMode(mShapeDrawable, tintMode);
-        }
-    }
-
-    @Override
-    void setRippleColor(int rippleColor) {
-        if (mRippleDrawable != null) {
-            DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
-        }
-    }
-
-    @Override
-    float getElevation() {
-        return mElevation;
-    }
-
-    @Override
-    void onElevationsChanged(float elevation, float pressedTranslationZ) {
-        if (mShadowDrawable != null) {
-            mShadowDrawable.setShadowSize(elevation, elevation + mPressedTranslationZ);
-            updatePadding();
-        }
-    }
-
-    @Override
-    void onDrawableStateChanged(int[] state) {
-        mStateListAnimator.setState(state);
-    }
-
-    @Override
-    void jumpDrawableToCurrentState() {
-        mStateListAnimator.jumpToCurrentState();
-    }
-
-    @Override
-    void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
-        if (isOrWillBeHidden()) {
-            // We either are or will soon be hidden, skip the call
-            return;
-        }
-
-        mAnimState = ANIM_STATE_HIDING;
-
-        Animation anim = android.view.animation.AnimationUtils.loadAnimation(
-                mView.getContext(), R.anim.design_fab_out);
-        anim.setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
-        anim.setDuration(SHOW_HIDE_ANIM_DURATION);
-        anim.setAnimationListener(new AnimationUtils.AnimationListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animation animation) {
-                mAnimState = ANIM_STATE_NONE;
-                mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE, fromUser);
-                if (listener != null) {
-                    listener.onHidden();
-                }
-            }
-        });
-        mView.startAnimation(anim);
-    }
-
-    @Override
-    void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
-        if (isOrWillBeShown()) {
-            // We either are or will soon be visible, skip the call
-            return;
-        }
-
-        mAnimState = ANIM_STATE_SHOWING;
-
-        mView.internalSetVisibility(View.VISIBLE, fromUser);
-        Animation anim = android.view.animation.AnimationUtils.loadAnimation(
-                mView.getContext(), R.anim.design_fab_in);
-        anim.setDuration(SHOW_HIDE_ANIM_DURATION);
-        anim.setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
-        anim.setAnimationListener(new AnimationListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animation animation) {
-                mAnimState = ANIM_STATE_NONE;
-                if (listener != null) {
-                    listener.onShown();
-                }
-            }
-        });
-        mView.startAnimation(anim);
-    }
-
-    @Override
-    void onCompatShadowChanged() {
-        // Ignore pre-v21
-    }
-
-    @Override
-    void getPadding(Rect rect) {
-        mShadowDrawable.getPadding(rect);
-    }
-
-    private ValueAnimatorCompat createAnimator(@NonNull ShadowAnimatorImpl impl) {
-        final ValueAnimatorCompat animator = mAnimatorCreator.createAnimator();
-        animator.setInterpolator(ANIM_INTERPOLATOR);
-        animator.setDuration(PRESSED_ANIM_DURATION);
-        animator.addListener(impl);
-        animator.addUpdateListener(impl);
-        animator.setFloatValues(0, 1);
-        return animator;
-    }
-
-    private abstract class ShadowAnimatorImpl extends ValueAnimatorCompat.AnimatorListenerAdapter
-            implements ValueAnimatorCompat.AnimatorUpdateListener {
-        private boolean mValidValues;
-        private float mShadowSizeStart;
-        private float mShadowSizeEnd;
-
-        @Override
-        public void onAnimationUpdate(ValueAnimatorCompat animator) {
-            if (!mValidValues) {
-                mShadowSizeStart = mShadowDrawable.getShadowSize();
-                mShadowSizeEnd = getTargetShadowSize();
-                mValidValues = true;
-            }
-
-            mShadowDrawable.setShadowSize(mShadowSizeStart
-                    + ((mShadowSizeEnd - mShadowSizeStart) * animator.getAnimatedFraction()));
-        }
-
-        @Override
-        public void onAnimationEnd(ValueAnimatorCompat animator) {
-            mShadowDrawable.setShadowSize(mShadowSizeEnd);
-            mValidValues = false;
-        }
-
-        /**
-         * @return the shadow size we want to animate to.
-         */
-        protected abstract float getTargetShadowSize();
-    }
-
-    private class ResetElevationAnimation extends ShadowAnimatorImpl {
-        ResetElevationAnimation() {
-        }
-
-        @Override
-        protected float getTargetShadowSize() {
-            return mElevation;
-        }
-    }
-
-    private class ElevateToTranslationZAnimation extends ShadowAnimatorImpl {
-        ElevateToTranslationZAnimation() {
-        }
-
-        @Override
-        protected float getTargetShadowSize() {
-            return mElevation + mPressedTranslationZ;
-        }
-    }
-
-    private class DisabledElevationAnimation extends ShadowAnimatorImpl {
-        DisabledElevationAnimation() {
-        }
-
-        @Override
-        protected float getTargetShadowSize() {
-            return 0f;
-        }
-    }
-
-    private static ColorStateList createColorStateList(int selectedColor) {
-        final int[][] states = new int[3][];
-        final int[] colors = new int[3];
-        int i = 0;
-
-        states[i] = FOCUSED_ENABLED_STATE_SET;
-        colors[i] = selectedColor;
-        i++;
-
-        states[i] = PRESSED_ENABLED_STATE_SET;
-        colors[i] = selectedColor;
-        i++;
-
-        // Default enabled state
-        states[i] = new int[0];
-        colors[i] = Color.TRANSPARENT;
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-}
\ No newline at end of file
diff --git a/design/gingerbread/android/support/design/widget/ValueAnimatorCompatImplGingerbread.java b/design/gingerbread/android/support/design/widget/ValueAnimatorCompatImplGingerbread.java
deleted file mode 100644
index 81e59b6..0000000
--- a/design/gingerbread/android/support/design/widget/ValueAnimatorCompatImplGingerbread.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.Interpolator;
-
-import java.util.ArrayList;
-
-/**
- * A 'fake' ValueAnimator implementation which uses a Runnable.
- */
-class ValueAnimatorCompatImplGingerbread extends ValueAnimatorCompat.Impl {
-
-    private static final int HANDLER_DELAY = 10;
-    private static final int DEFAULT_DURATION = 200;
-
-    private static final Handler sHandler = new Handler(Looper.getMainLooper());
-
-    private long mStartTime;
-    private boolean mIsRunning;
-    private float mAnimatedFraction;
-
-    private final int[] mIntValues = new int[2];
-    private final float[] mFloatValues = new float[2];
-
-    private long mDuration = DEFAULT_DURATION;
-    private Interpolator mInterpolator;
-    private ArrayList<AnimatorListenerProxy> mListeners;
-    private ArrayList<AnimatorUpdateListenerProxy> mUpdateListeners;
-
-    private final Runnable mRunnable = new Runnable() {
-        public void run() {
-            update();
-        }
-    };
-
-    @Override
-    public void start() {
-        if (mIsRunning) {
-            // If we're already running, ignore
-            return;
-        }
-        if (mInterpolator == null) {
-            mInterpolator = new AccelerateDecelerateInterpolator();
-        }
-        mIsRunning = true;
-
-        // Reset the animated fraction
-        mAnimatedFraction = 0f;
-
-        startInternal();
-    }
-
-    final void startInternal() {
-        mStartTime = SystemClock.uptimeMillis();
-        dispatchAnimationUpdate();
-        dispatchAnimationStart();
-        // Now start our animation ticker
-        sHandler.postDelayed(mRunnable, HANDLER_DELAY);
-    }
-
-    @Override
-    public boolean isRunning() {
-        return mIsRunning;
-    }
-
-    @Override
-    public void setInterpolator(Interpolator interpolator) {
-        mInterpolator = interpolator;
-    }
-
-    @Override
-    public void addListener(AnimatorListenerProxy listener) {
-        if (mListeners == null) {
-            mListeners = new ArrayList<>();
-        }
-        mListeners.add(listener);
-    }
-
-    @Override
-    public void addUpdateListener(AnimatorUpdateListenerProxy updateListener) {
-        if (mUpdateListeners == null) {
-            mUpdateListeners = new ArrayList<>();
-        }
-        mUpdateListeners.add(updateListener);
-    }
-
-    @Override
-    public void setIntValues(int from, int to) {
-        mIntValues[0] = from;
-        mIntValues[1] = to;
-    }
-
-    @Override
-    public int getAnimatedIntValue() {
-        return AnimationUtils.lerp(mIntValues[0], mIntValues[1], getAnimatedFraction());
-    }
-
-    @Override
-    public void setFloatValues(float from, float to) {
-        mFloatValues[0] = from;
-        mFloatValues[1] = to;
-    }
-
-    @Override
-    public float getAnimatedFloatValue() {
-        return AnimationUtils.lerp(mFloatValues[0], mFloatValues[1], getAnimatedFraction());
-    }
-
-    @Override
-    public void setDuration(long duration) {
-        mDuration = duration;
-    }
-
-    @Override
-    public void cancel() {
-        mIsRunning = false;
-        sHandler.removeCallbacks(mRunnable);
-
-        dispatchAnimationCancel();
-        dispatchAnimationEnd();
-    }
-
-    @Override
-    public float getAnimatedFraction() {
-        return mAnimatedFraction;
-    }
-
-    @Override
-    public void end() {
-        if (mIsRunning) {
-            mIsRunning = false;
-            sHandler.removeCallbacks(mRunnable);
-            // Set our animated fraction to 1
-            mAnimatedFraction = 1f;
-            dispatchAnimationUpdate();
-            dispatchAnimationEnd();
-        }
-    }
-
-    @Override
-    public long getDuration() {
-        return mDuration;
-    }
-
-    final void update() {
-        if (mIsRunning) {
-            // Update the animated fraction
-            final long elapsed = SystemClock.uptimeMillis() - mStartTime;
-            final float linearFraction = MathUtils.constrain(elapsed / (float) mDuration, 0f, 1f);
-            mAnimatedFraction = mInterpolator != null
-                    ? mInterpolator.getInterpolation(linearFraction)
-                    : linearFraction;
-
-            // If we're running, dispatch to the update listeners
-            dispatchAnimationUpdate();
-
-            // Check to see if we've passed the animation duration
-            if (SystemClock.uptimeMillis() >= (mStartTime + mDuration)) {
-                mIsRunning = false;
-
-                dispatchAnimationEnd();
-            }
-        }
-
-        if (mIsRunning) {
-            // If we're still running, post another delayed runnable
-            sHandler.postDelayed(mRunnable, HANDLER_DELAY);
-        }
-    }
-
-    private void dispatchAnimationUpdate() {
-        if (mUpdateListeners != null) {
-            for (int i = 0, count = mUpdateListeners.size(); i < count; i++) {
-                mUpdateListeners.get(i).onAnimationUpdate();
-            }
-        }
-    }
-
-    private void dispatchAnimationStart() {
-        if (mListeners != null) {
-            for (int i = 0, count = mListeners.size(); i < count; i++) {
-                mListeners.get(i).onAnimationStart();
-            }
-        }
-    }
-
-    private void dispatchAnimationCancel() {
-        if (mListeners != null) {
-            for (int i = 0, count = mListeners.size(); i < count; i++) {
-                mListeners.get(i).onAnimationCancel();
-            }
-        }
-    }
-
-    private void dispatchAnimationEnd() {
-        if (mListeners != null) {
-            for (int i = 0, count = mListeners.size(); i < count; i++) {
-                mListeners.get(i).onAnimationEnd();
-            }
-        }
-    }
-}
diff --git a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
deleted file mode 100644
index b88015a..0000000
--- a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.support.annotation.RequiresApi;
-import android.view.animation.Interpolator;
-
-@RequiresApi(12)
-class ValueAnimatorCompatImplHoneycombMr1 extends ValueAnimatorCompat.Impl {
-
-    private final ValueAnimator mValueAnimator;
-
-    ValueAnimatorCompatImplHoneycombMr1() {
-        mValueAnimator = new ValueAnimator();
-    }
-
-    @Override
-    public void start() {
-        mValueAnimator.start();
-    }
-
-    @Override
-    public boolean isRunning() {
-        return mValueAnimator.isRunning();
-    }
-
-    @Override
-    public void setInterpolator(Interpolator interpolator) {
-        mValueAnimator.setInterpolator(interpolator);
-    }
-
-    @Override
-    public void addUpdateListener(final AnimatorUpdateListenerProxy updateListener) {
-        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator valueAnimator) {
-                updateListener.onAnimationUpdate();
-            }
-        });
-    }
-
-    @Override
-    public void addListener(final AnimatorListenerProxy listener) {
-        mValueAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationStart(Animator animator) {
-                listener.onAnimationStart();
-            }
-
-            @Override
-            public void onAnimationEnd(Animator animator) {
-                listener.onAnimationEnd();
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animator) {
-                listener.onAnimationCancel();
-            }
-        });
-    }
-
-    @Override
-    public void setIntValues(int from, int to) {
-        mValueAnimator.setIntValues(from, to);
-    }
-
-    @Override
-    public int getAnimatedIntValue() {
-        return (int) mValueAnimator.getAnimatedValue();
-    }
-
-    @Override
-    public void setFloatValues(float from, float to) {
-        mValueAnimator.setFloatValues(from, to);
-    }
-
-    @Override
-    public float getAnimatedFloatValue() {
-        return (float) mValueAnimator.getAnimatedValue();
-    }
-
-    @Override
-    public void setDuration(long duration) {
-        mValueAnimator.setDuration(duration);
-    }
-
-    @Override
-    public void cancel() {
-        mValueAnimator.cancel();
-    }
-
-    @Override
-    public float getAnimatedFraction() {
-        return mValueAnimator.getAnimatedFraction();
-    }
-
-    @Override
-    public void end() {
-        mValueAnimator.end();
-    }
-
-    @Override
-    public long getDuration() {
-        return mValueAnimator.getDuration();
-    }
-}
diff --git a/design/honeycomb/android/support/design/widget/ViewGroupUtilsHoneycomb.java b/design/honeycomb/android/support/design/widget/ViewGroupUtilsHoneycomb.java
deleted file mode 100644
index c35c1da..0000000
--- a/design/honeycomb/android/support/design/widget/ViewGroupUtilsHoneycomb.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.support.annotation.RequiresApi;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-@RequiresApi(11)
-class ViewGroupUtilsHoneycomb {
-    private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
-    private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
-
-    public static void offsetDescendantRect(ViewGroup group, View child, Rect rect) {
-        Matrix m = sMatrix.get();
-        if (m == null) {
-            m = new Matrix();
-            sMatrix.set(m);
-        } else {
-            m.reset();
-        }
-
-        offsetDescendantMatrix(group, child, m);
-
-        RectF rectF = sRectF.get();
-        if (rectF == null) {
-            rectF = new RectF();
-            sRectF.set(rectF);
-        }
-        rectF.set(rect);
-        m.mapRect(rectF);
-        rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f),
-                (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f));
-    }
-
-    static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) {
-        final ViewParent parent = view.getParent();
-        if (parent instanceof View && parent != target) {
-            final View vp = (View) parent;
-            offsetDescendantMatrix(target, vp, m);
-            m.preTranslate(-vp.getScrollX(), -vp.getScrollY());
-        }
-
-        m.preTranslate(view.getLeft(), view.getTop());
-
-        if (!view.getMatrix().isIdentity()) {
-            m.preConcat(view.getMatrix());
-        }
-    }
-}
diff --git a/design/ics/android/support/design/internal/BottomNavigationAnimationHelperIcs.java b/design/ics/android/support/design/internal/BottomNavigationAnimationHelperIcs.java
deleted file mode 100644
index 6681d0b..0000000
--- a/design/ics/android/support/design/internal/BottomNavigationAnimationHelperIcs.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016 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.support.design.internal;
-
-import android.support.transition.AutoTransition;
-import android.support.transition.TransitionManager;
-import android.support.transition.TransitionSet;
-import android.support.v4.view.animation.FastOutSlowInInterpolator;
-import android.view.ViewGroup;
-
-class BottomNavigationAnimationHelperIcs extends BottomNavigationAnimationHelperBase {
-    private static final long ACTIVE_ANIMATION_DURATION_MS = 115L;
-
-    private final TransitionSet mSet;
-
-    BottomNavigationAnimationHelperIcs() {
-        mSet = new AutoTransition();
-        mSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
-        mSet.setDuration(ACTIVE_ANIMATION_DURATION_MS);
-        mSet.setInterpolator(new FastOutSlowInInterpolator());
-        TextScale textScale = new TextScale();
-        mSet.addTransition(textScale);
-    }
-
-    void beginDelayedTransition(ViewGroup view) {
-        TransitionManager.beginDelayedTransition(view, mSet);
-    }
-}
diff --git a/design/ics/android/support/design/widget/FloatingActionButtonIcs.java b/design/ics/android/support/design/widget/FloatingActionButtonIcs.java
deleted file mode 100644
index 5211926..0000000
--- a/design/ics/android/support/design/widget/FloatingActionButtonIcs.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.os.Build;
-import android.support.annotation.Nullable;
-import android.support.annotation.RequiresApi;
-import android.support.v4.view.ViewCompat;
-import android.view.View;
-
-@RequiresApi(14)
-class FloatingActionButtonIcs extends FloatingActionButtonGingerbread {
-
-    private float mRotation;
-
-    FloatingActionButtonIcs(VisibilityAwareImageButton view,
-            ShadowViewDelegate shadowViewDelegate, ValueAnimatorCompat.Creator animatorCreator) {
-        super(view, shadowViewDelegate, animatorCreator);
-        mRotation = mView.getRotation();
-    }
-
-    @Override
-    boolean requirePreDrawListener() {
-        return true;
-    }
-
-    @Override
-    void onPreDraw() {
-        final float rotation = mView.getRotation();
-        if (mRotation != rotation) {
-            mRotation = rotation;
-            updateFromViewRotation();
-        }
-    }
-
-    @Override
-    void hide(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
-        if (isOrWillBeHidden()) {
-            // We either are or will soon be hidden, skip the call
-            return;
-        }
-
-        mView.animate().cancel();
-
-        if (shouldAnimateVisibilityChange()) {
-            mAnimState = ANIM_STATE_HIDING;
-
-            mView.animate()
-                    .scaleX(0f)
-                    .scaleY(0f)
-                    .alpha(0f)
-                    .setDuration(SHOW_HIDE_ANIM_DURATION)
-                    .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
-                    .setListener(new AnimatorListenerAdapter() {
-                        private boolean mCancelled;
-
-                        @Override
-                        public void onAnimationStart(Animator animation) {
-                            mView.internalSetVisibility(View.VISIBLE, fromUser);
-                            mCancelled = false;
-                        }
-
-                        @Override
-                        public void onAnimationCancel(Animator animation) {
-                            mCancelled = true;
-                        }
-
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mAnimState = ANIM_STATE_NONE;
-
-                            if (!mCancelled) {
-                                mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE,
-                                        fromUser);
-                                if (listener != null) {
-                                    listener.onHidden();
-                                }
-                            }
-                        }
-                    });
-        } else {
-            // If the view isn't laid out, or we're in the editor, don't run the animation
-            mView.internalSetVisibility(fromUser ? View.GONE : View.INVISIBLE, fromUser);
-            if (listener != null) {
-                listener.onHidden();
-            }
-        }
-    }
-
-    @Override
-    void show(@Nullable final InternalVisibilityChangedListener listener, final boolean fromUser) {
-        if (isOrWillBeShown()) {
-            // We either are or will soon be visible, skip the call
-            return;
-        }
-
-        mView.animate().cancel();
-
-        if (shouldAnimateVisibilityChange()) {
-            mAnimState = ANIM_STATE_SHOWING;
-
-            if (mView.getVisibility() != View.VISIBLE) {
-                // If the view isn't visible currently, we'll animate it from a single pixel
-                mView.setAlpha(0f);
-                mView.setScaleY(0f);
-                mView.setScaleX(0f);
-            }
-
-            mView.animate()
-                    .scaleX(1f)
-                    .scaleY(1f)
-                    .alpha(1f)
-                    .setDuration(SHOW_HIDE_ANIM_DURATION)
-                    .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR)
-                    .setListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationStart(Animator animation) {
-                            mView.internalSetVisibility(View.VISIBLE, fromUser);
-                        }
-
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            mAnimState = ANIM_STATE_NONE;
-                            if (listener != null) {
-                                listener.onShown();
-                            }
-                        }
-                    });
-        } else {
-            mView.internalSetVisibility(View.VISIBLE, fromUser);
-            mView.setAlpha(1f);
-            mView.setScaleY(1f);
-            mView.setScaleX(1f);
-            if (listener != null) {
-                listener.onShown();
-            }
-        }
-    }
-
-    private boolean shouldAnimateVisibilityChange() {
-        return ViewCompat.isLaidOut(mView) && !mView.isInEditMode();
-    }
-
-    private void updateFromViewRotation() {
-        if (Build.VERSION.SDK_INT == 19) {
-            // KitKat seems to have an issue with views which are rotated with angles which are
-            // not divisible by 90. Worked around by moving to software rendering in these cases.
-            if ((mRotation % 90) != 0) {
-                if (mView.getLayerType() != View.LAYER_TYPE_SOFTWARE) {
-                    mView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
-                }
-            } else {
-                if (mView.getLayerType() != View.LAYER_TYPE_NONE) {
-                    mView.setLayerType(View.LAYER_TYPE_NONE, null);
-                }
-            }
-        }
-
-        // Offset any View rotation
-        if (mShadowDrawable != null) {
-            mShadowDrawable.setRotation(-mRotation);
-        }
-        if (mBorderDrawable != null) {
-            mBorderDrawable.setRotation(-mRotation);
-        }
-    }
-}
diff --git a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
index db7f43a..d7c258f 100644
--- a/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
+++ b/design/lollipop/android/support/design/widget/FloatingActionButtonLollipop.java
@@ -33,13 +33,13 @@
 import android.view.View;
 
 @RequiresApi(21)
-class FloatingActionButtonLollipop extends FloatingActionButtonIcs {
+class FloatingActionButtonLollipop extends FloatingActionButtonImpl {
 
     private InsetDrawable mInsetDrawable;
 
     FloatingActionButtonLollipop(VisibilityAwareImageButton view,
-            ShadowViewDelegate shadowViewDelegate, ValueAnimatorCompat.Creator animatorCreator) {
-        super(view, shadowViewDelegate, animatorCreator);
+            ShadowViewDelegate shadowViewDelegate) {
+        super(view, shadowViewDelegate);
     }
 
     @Override
diff --git a/design/res/anim/design_fab_in.xml b/design/res/anim/design_fab_in.xml
deleted file mode 100644
index 294050f..0000000
--- a/design/res/anim/design_fab_in.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 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.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha android:fromAlpha="0.0"
-           android:toAlpha="1.0"/>
-
-    <scale android:fromXScale="0.0"
-           android:fromYScale="0.0"
-           android:toXScale="1.0"
-           android:toYScale="1.0"
-           android:pivotX="50%"
-           android:pivotY="50%"/>
-
-</set>
diff --git a/design/res/anim/design_fab_out.xml b/design/res/anim/design_fab_out.xml
deleted file mode 100644
index 0f80a9a..0000000
--- a/design/res/anim/design_fab_out.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2015 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.
-  -->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <alpha android:fromAlpha="1.0"
-           android:toAlpha="0.0"/>
-
-    <scale android:fromXScale="1.0"
-           android:fromYScale="1.0"
-           android:toXScale="0.0"
-           android:toYScale="0.0"
-           android:pivotX="50%"
-           android:pivotY="50%"/>
-
-</set>
diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java
index 82d983e..a80393b 100644
--- a/design/src/android/support/design/internal/BottomNavigationMenuView.java
+++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java
@@ -21,12 +21,15 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.os.Build;
 import android.support.annotation.Nullable;
 import android.support.annotation.RestrictTo;
 import android.support.design.R;
+import android.support.transition.AutoTransition;
+import android.support.transition.TransitionManager;
+import android.support.transition.TransitionSet;
 import android.support.v4.util.Pools;
 import android.support.v4.view.ViewCompat;
+import android.support.v4.view.animation.FastOutSlowInInterpolator;
 import android.support.v7.view.menu.MenuBuilder;
 import android.support.v7.view.menu.MenuItemImpl;
 import android.support.v7.view.menu.MenuView;
@@ -39,12 +42,14 @@
  */
 @RestrictTo(LIBRARY_GROUP)
 public class BottomNavigationMenuView extends ViewGroup implements MenuView {
+    private static final long ACTIVE_ANIMATION_DURATION_MS = 115L;
+
+    private final TransitionSet mSet;
     private final int mInactiveItemMaxWidth;
     private final int mInactiveItemMinWidth;
     private final int mActiveItemMaxWidth;
     private final int mItemHeight;
     private final OnClickListener mOnClickListener;
-    private final BottomNavigationAnimationHelperBase mAnimationHelper;
     private static final Pools.Pool<BottomNavigationItemView> sItemPool =
             new Pools.SynchronizedPool<>(5);
 
@@ -75,11 +80,11 @@
                 R.dimen.design_bottom_navigation_active_item_max_width);
         mItemHeight = res.getDimensionPixelSize(R.dimen.design_bottom_navigation_height);
 
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
-            mAnimationHelper = new BottomNavigationAnimationHelperIcs();
-        } else {
-            mAnimationHelper = new BottomNavigationAnimationHelperBase();
-        }
+        mSet = new AutoTransition();
+        mSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
+        mSet.setDuration(ACTIVE_ANIMATION_DURATION_MS);
+        mSet.setInterpolator(new FastOutSlowInInterpolator());
+        mSet.addTransition(new TextScale());
 
         mOnClickListener = new OnClickListener() {
             @Override
@@ -299,7 +304,7 @@
     private void activateNewButton(int newButton) {
         if (mActiveButton == newButton) return;
 
-        mAnimationHelper.beginDelayedTransition(this);
+        TransitionManager.beginDelayedTransition(this, mSet);
 
         mMenu.getItem(newButton).setChecked(true);
 
diff --git a/design/ics/android/support/design/internal/TextScale.java b/design/src/android/support/design/internal/TextScale.java
similarity index 96%
rename from design/ics/android/support/design/internal/TextScale.java
rename to design/src/android/support/design/internal/TextScale.java
index 1687469..06c9472 100644
--- a/design/ics/android/support/design/internal/TextScale.java
+++ b/design/src/android/support/design/internal/TextScale.java
@@ -19,6 +19,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator;
 import android.support.annotation.RequiresApi;
+import android.support.annotation.RestrictTo;
 import android.support.transition.Transition;
 import android.support.transition.TransitionValues;
 import android.view.ViewGroup;
@@ -29,6 +30,7 @@
 /**
  * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @RequiresApi(14)
 public class TextScale extends Transition {
     private static final String PROPNAME_SCALE = "android:textscale:scale";
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 2f86de7..eeac690 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -19,6 +19,7 @@
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 import static android.support.design.widget.ViewUtils.objectEquals;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
@@ -791,7 +792,7 @@
         private boolean mSkipNestedPreScroll;
         private boolean mWasNestedFlung;
 
-        private ValueAnimatorCompat mOffsetAnimator;
+        private ValueAnimator mOffsetAnimator;
 
         private int mOffsetToChildIndexOnLayout = INVALID_POSITION;
         private boolean mOffsetToChildIndexOnLayoutIsMinHeight;
@@ -951,13 +952,13 @@
             }
 
             if (mOffsetAnimator == null) {
-                mOffsetAnimator = ViewUtils.createAnimator();
+                mOffsetAnimator = new ValueAnimator();
                 mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
-                mOffsetAnimator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+                mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                     @Override
-                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
+                    public void onAnimationUpdate(ValueAnimator animation) {
                         setHeaderTopBottomOffset(coordinatorLayout, child,
-                                animator.getAnimatedIntValue());
+                                (int) animation.getAnimatedValue());
                     }
                 });
             } else {
diff --git a/design/src/android/support/design/widget/BaseTransientBottomBar.java b/design/src/android/support/design/widget/BaseTransientBottomBar.java
index bdb3eae..ca7f15f 100644
--- a/design/src/android/support/design/widget/BaseTransientBottomBar.java
+++ b/design/src/android/support/design/widget/BaseTransientBottomBar.java
@@ -19,6 +19,9 @@
 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
 import static android.support.design.widget.AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.os.Build;
@@ -497,29 +500,29 @@
             } else {
                 ViewCompat.setTranslationY(mView, viewHeight);
             }
-            final ValueAnimatorCompat animator = ViewUtils.createAnimator();
+            final ValueAnimator animator = new ValueAnimator();
             animator.setIntValues(viewHeight, 0);
             animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
             animator.setDuration(ANIMATION_DURATION);
-            animator.addListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
+            animator.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationStart(ValueAnimatorCompat animator) {
+                public void onAnimationStart(Animator animator) {
                     mContentViewCallback.animateContentIn(
                             ANIMATION_DURATION - ANIMATION_FADE_DURATION,
                             ANIMATION_FADE_DURATION);
                 }
 
                 @Override
-                public void onAnimationEnd(ValueAnimatorCompat animator) {
+                public void onAnimationEnd(Animator animator) {
                     onViewShown();
                 }
             });
-            animator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 private int mPreviousAnimatedIntValue = viewHeight;
 
                 @Override
-                public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                    int currentAnimatedIntValue = animator.getAnimatedIntValue();
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    int currentAnimatedIntValue = (int) animator.getAnimatedValue();
                     if (USE_OFFSET_API) {
                         ViewCompat.offsetTopAndBottom(mView,
                                 currentAnimatedIntValue - mPreviousAnimatedIntValue);
@@ -553,27 +556,27 @@
 
     private void animateViewOut(final int event) {
         if (Build.VERSION.SDK_INT >= 12) {
-            final ValueAnimatorCompat animator = ViewUtils.createAnimator();
+            final ValueAnimator animator = new ValueAnimator();
             animator.setIntValues(0, mView.getHeight());
             animator.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
             animator.setDuration(ANIMATION_DURATION);
-            animator.addListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
+            animator.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationStart(ValueAnimatorCompat animator) {
+                public void onAnimationStart(Animator animator) {
                     mContentViewCallback.animateContentOut(0, ANIMATION_FADE_DURATION);
                 }
 
                 @Override
-                public void onAnimationEnd(ValueAnimatorCompat animator) {
+                public void onAnimationEnd(Animator animator) {
                     onViewHidden(event);
                 }
             });
-            animator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+            animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 private int mPreviousAnimatedIntValue = 0;
 
                 @Override
-                public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                    int currentAnimatedIntValue = animator.getAnimatedIntValue();
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    int currentAnimatedIntValue = (int) animator.getAnimatedValue();
                     if (USE_OFFSET_API) {
                         ViewCompat.offsetTopAndBottom(mView,
                                 currentAnimatedIntValue - mPreviousAnimatedIntValue);
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index fdd25d5..cb24537 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -20,6 +20,7 @@
 import static android.support.design.widget.MathUtils.constrain;
 import static android.support.design.widget.ViewUtils.objectEquals;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
@@ -124,7 +125,7 @@
     Drawable mStatusBarScrim;
     private int mScrimAlpha;
     private boolean mScrimsAreShown;
-    private ValueAnimatorCompat mScrimAnimator;
+    private ValueAnimator mScrimAnimator;
     private long mScrimAnimationDuration;
     private int mScrimVisibleHeightTrigger = -1;
 
@@ -594,16 +595,16 @@
     private void animateScrim(int targetAlpha) {
         ensureToolbar();
         if (mScrimAnimator == null) {
-            mScrimAnimator = ViewUtils.createAnimator();
+            mScrimAnimator = new ValueAnimator();
             mScrimAnimator.setDuration(mScrimAnimationDuration);
             mScrimAnimator.setInterpolator(
                     targetAlpha > mScrimAlpha
                             ? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
                             : AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
-            mScrimAnimator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+            mScrimAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 @Override
-                public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                    setScrimAlpha(animator.getAnimatedIntValue());
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    setScrimAlpha((int) animator.getAnimatedValue());
                 }
             });
         } else if (mScrimAnimator.isRunning()) {
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index ed698a1..4c27979 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -1171,11 +1171,18 @@
     }
 
     /**
-     * Return the given gravity value or the default if the passed value is NO_GRAVITY.
-     * This should be used for children that are not anchored to another view or a keyline.
+     * Return the given gravity value, but if either or both of the axes doesn't have any gravity
+     * specified, the default value (start or top) is specified. This should be used for children
+     * that are not anchored to another view or a keyline.
      */
     private static int resolveGravity(int gravity) {
-        return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity;
+        if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
+            gravity |= GravityCompat.START;
+        }
+        if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.NO_GRAVITY) {
+            gravity |= Gravity.TOP;
+        }
+        return gravity;
     }
 
     /**
@@ -2551,8 +2558,10 @@
 
         /**
          * A {@link Gravity} value describing how this child view should lay out.
-         * If an {@link #setAnchorId(int) anchor} is also specified, the gravity describes
-         * how this child view should be positioned relative to its anchored position.
+         * If either or both of the axes are not specified, they are treated by CoordinatorLayout
+         * as {@link Gravity#TOP} or {@link GravityCompat#START}. If an
+         * {@link #setAnchorId(int) anchor} is also specified, the gravity describes how this child
+         * view should be positioned relative to its anchored position.
          */
         public int gravity = Gravity.NO_GRAVITY;
 
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index f8d94d2..5488b2b 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -795,16 +795,10 @@
     }
 
     private FloatingActionButtonImpl createImpl() {
-        final int sdk = Build.VERSION.SDK_INT;
-        if (sdk >= 21) {
-            return new FloatingActionButtonLollipop(this, new ShadowDelegateImpl(),
-                    ViewUtils.DEFAULT_ANIMATOR_CREATOR);
-        } else if (sdk >= 14) {
-            return new FloatingActionButtonIcs(this, new ShadowDelegateImpl(),
-                    ViewUtils.DEFAULT_ANIMATOR_CREATOR);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return new FloatingActionButtonLollipop(this, new ShadowDelegateImpl());
         } else {
-            return new FloatingActionButtonGingerbread(this, new ShadowDelegateImpl(),
-                    ViewUtils.DEFAULT_ANIMATOR_CREATOR);
+            return new FloatingActionButtonImpl(this, new ShadowDelegateImpl());
         }
     }
 
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index a096a3d..82add6f 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -324,6 +324,25 @@
         public SnackbarLayout(Context context, AttributeSet attrs) {
             super(context, attrs);
         }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+            // Work around our backwards-compatible refactoring of Snackbar and inner content
+            // being inflated against snackbar's parent (instead of against the snackbar itself).
+            // Every child that is width=MATCH_PARENT is remeasured again and given the full width
+            // minus the paddings.
+            int childCount = getChildCount();
+            int availableWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+            for (int i = 0; i < childCount; i++) {
+                View child = getChildAt(i);
+                if (child.getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
+                    child.measure(MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.EXACTLY),
+                            MeasureSpec.makeMeasureSpec(child.getMeasuredHeight(),
+                                    MeasureSpec.EXACTLY));
+                }
+            }
+        }
     }
 }
 
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 4d392f2..34301bb 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -21,6 +21,9 @@
 import static android.support.v4.view.ViewPager.SCROLL_STATE_IDLE;
 import static android.support.v4.view.ViewPager.SCROLL_STATE_SETTLING;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -268,7 +271,7 @@
     private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
     private OnTabSelectedListener mCurrentVpSelectedListener;
 
-    private ValueAnimatorCompat mScrollAnimator;
+    private ValueAnimator mScrollAnimator;
 
     ViewPager mViewPager;
     private PagerAdapter mPagerAdapter;
@@ -1082,13 +1085,13 @@
 
         if (startScrollX != targetScrollX) {
             if (mScrollAnimator == null) {
-                mScrollAnimator = ViewUtils.createAnimator();
+                mScrollAnimator = new ValueAnimator();
                 mScrollAnimator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                 mScrollAnimator.setDuration(ANIMATION_DURATION);
-                mScrollAnimator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+                mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                     @Override
-                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                        scrollTo(animator.getAnimatedIntValue(), 0);
+                    public void onAnimationUpdate(ValueAnimator animator) {
+                        scrollTo((int) animator.getAnimatedValue(), 0);
                     }
                 });
             }
@@ -1774,7 +1777,7 @@
         private int mIndicatorLeft = -1;
         private int mIndicatorRight = -1;
 
-        private ValueAnimatorCompat mIndicatorAnimator;
+        private ValueAnimator mIndicatorAnimator;
 
         SlidingTabStrip(Context context) {
             super(context);
@@ -1971,22 +1974,22 @@
             }
 
             if (startLeft != targetLeft || startRight != targetRight) {
-                ValueAnimatorCompat animator = mIndicatorAnimator = ViewUtils.createAnimator();
+                ValueAnimator animator = mIndicatorAnimator = new ValueAnimator();
                 animator.setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR);
                 animator.setDuration(duration);
                 animator.setFloatValues(0, 1);
-                animator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                     @Override
-                    public void onAnimationUpdate(ValueAnimatorCompat animator) {
+                    public void onAnimationUpdate(ValueAnimator animator) {
                         final float fraction = animator.getAnimatedFraction();
                         setIndicatorPosition(
                                 AnimationUtils.lerp(startLeft, targetLeft, fraction),
                                 AnimationUtils.lerp(startRight, targetRight, fraction));
                     }
                 });
-                animator.addListener(new ValueAnimatorCompat.AnimatorListenerAdapter() {
+                animator.addListener(new AnimatorListenerAdapter() {
                     @Override
-                    public void onAnimationEnd(ValueAnimatorCompat animator) {
+                    public void onAnimationEnd(Animator animator) {
                         mSelectedPosition = position;
                         mSelectionOffset = 0f;
                     }
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 10da563..a879b9f 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -16,6 +16,7 @@
 
 package android.support.design.widget;
 
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -168,7 +169,7 @@
     final CollapsingTextHelper mCollapsingTextHelper = new CollapsingTextHelper(this);
 
     private boolean mHintAnimationEnabled;
-    private ValueAnimatorCompat mAnimator;
+    private ValueAnimator mAnimator;
 
     private boolean mHasReconstructedEditTextBackground;
     private boolean mInDrawableStateChanged;
@@ -1420,13 +1421,13 @@
             return;
         }
         if (mAnimator == null) {
-            mAnimator = ViewUtils.createAnimator();
+            mAnimator = new ValueAnimator();
             mAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
             mAnimator.setDuration(ANIMATION_DURATION);
-            mAnimator.addUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+            mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                 @Override
-                public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                    mCollapsingTextHelper.setExpansionFraction(animator.getAnimatedFloatValue());
+                public void onAnimationUpdate(ValueAnimator animator) {
+                    mCollapsingTextHelper.setExpansionFraction((float) animator.getAnimatedValue());
                 }
             });
         }
diff --git a/design/src/android/support/design/widget/ViewGroupUtils.java b/design/src/android/support/design/widget/ViewGroupUtils.java
index bb5e1b6..0545516 100644
--- a/design/src/android/support/design/widget/ViewGroupUtils.java
+++ b/design/src/android/support/design/widget/ViewGroupUtils.java
@@ -16,51 +16,16 @@
 
 package android.support.design.widget;
 
+import android.graphics.Matrix;
 import android.graphics.Rect;
-import android.os.Build;
+import android.graphics.RectF;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 
 class ViewGroupUtils {
-
-    private interface ViewGroupUtilsImpl {
-        void offsetDescendantRect(ViewGroup parent, View child, Rect rect);
-    }
-
-    private static class ViewGroupUtilsImplBase implements ViewGroupUtilsImpl {
-        ViewGroupUtilsImplBase() {
-        }
-
-        @Override
-        public void offsetDescendantRect(ViewGroup parent, View child, Rect rect) {
-            parent.offsetDescendantRectToMyCoords(child, rect);
-            // View#offsetDescendantRectToMyCoords includes scroll offsets of the last child.
-            // We need to reverse it here so that we get the rect of the view itself rather
-            // than its content.
-            rect.offset(child.getScrollX(), child.getScrollY());
-        }
-    }
-
-    private static class ViewGroupUtilsImplHoneycomb implements ViewGroupUtilsImpl {
-        ViewGroupUtilsImplHoneycomb() {
-        }
-
-        @Override
-        public void offsetDescendantRect(ViewGroup parent, View child, Rect rect) {
-            ViewGroupUtilsHoneycomb.offsetDescendantRect(parent, child, rect);
-        }
-    }
-
-    private static final ViewGroupUtilsImpl IMPL;
-
-    static {
-        final int version = Build.VERSION.SDK_INT;
-        if (version >= 11) {
-            IMPL = new ViewGroupUtilsImplHoneycomb();
-        } else {
-            IMPL = new ViewGroupUtilsImplBase();
-        }
-    }
+    private static final ThreadLocal<Matrix> sMatrix = new ThreadLocal<>();
+    private static final ThreadLocal<RectF> sRectF = new ThreadLocal<>();
 
     /**
      * This is a port of the common
@@ -72,7 +37,25 @@
      * @param rect (in/out) the rect to offset from descendant to this view's coordinate system
      */
     static void offsetDescendantRect(ViewGroup parent, View descendant, Rect rect) {
-        IMPL.offsetDescendantRect(parent, descendant, rect);
+        Matrix m = sMatrix.get();
+        if (m == null) {
+            m = new Matrix();
+            sMatrix.set(m);
+        } else {
+            m.reset();
+        }
+
+        offsetDescendantMatrix(parent, descendant, m);
+
+        RectF rectF = sRectF.get();
+        if (rectF == null) {
+            rectF = new RectF();
+            sRectF.set(rectF);
+        }
+        rectF.set(rect);
+        m.mapRect(rectF);
+        rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f),
+                (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f));
     }
 
     /**
@@ -87,4 +70,18 @@
         offsetDescendantRect(parent, descendant, out);
     }
 
+    private static void offsetDescendantMatrix(ViewParent target, View view, Matrix m) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View && parent != target) {
+            final View vp = (View) parent;
+            offsetDescendantMatrix(target, vp, m);
+            m.preTranslate(-vp.getScrollX(), -vp.getScrollY());
+        }
+
+        m.preTranslate(view.getLeft(), view.getTop());
+
+        if (!view.getMatrix().isIdentity()) {
+            m.preConcat(view.getMatrix());
+        }
+    }
 }
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index f49d836..1762b09 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -17,24 +17,8 @@
 package android.support.design.widget;
 
 import android.graphics.PorterDuff;
-import android.os.Build;
 
 class ViewUtils {
-
-    static final ValueAnimatorCompat.Creator DEFAULT_ANIMATOR_CREATOR
-            = new ValueAnimatorCompat.Creator() {
-        @Override
-        public ValueAnimatorCompat createAnimator() {
-            return new ValueAnimatorCompat(Build.VERSION.SDK_INT >= 12
-                    ? new ValueAnimatorCompatImplHoneycombMr1()
-                    : new ValueAnimatorCompatImplGingerbread());
-        }
-    };
-
-    static ValueAnimatorCompat createAnimator() {
-        return DEFAULT_ANIMATOR_CREATOR.createAnimator();
-    }
-
     static boolean objectEquals(Object a, Object b) {
         return (a == b) || (a != null && a.equals(b));
     }
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
index f8e4f99..73ad193 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
+++ b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
@@ -45,6 +45,7 @@
 import android.support.design.widget.CoordinatorLayout.Behavior;
 import android.support.test.filters.MediumTest;
 import android.support.test.filters.SdkSuppress;
+import android.support.v4.view.GravityCompat;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
 import android.view.Gravity;
@@ -129,6 +130,58 @@
     }
 
     @Test
+    public void testLayoutChildren() throws Throwable {
+        final Instrumentation instrumentation = getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+        final View view = new View(col.getContext());
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                col.addView(view, 100, 100);
+            }
+        });
+        instrumentation.waitForIdleSync();
+        int horizontallyCentered = (col.getWidth() - view.getWidth()) / 2;
+        int end = col.getWidth() - view.getWidth();
+        int verticallyCentered = (col.getHeight() - view.getHeight()) / 2;
+        int bottom = col.getHeight() - view.getHeight();
+        final int[][] testCases = {
+                // gravity, expected left, expected top
+                {Gravity.NO_GRAVITY, 0, 0},
+                {Gravity.LEFT, 0, 0},
+                {GravityCompat.START, 0, 0},
+                {Gravity.TOP, 0, 0},
+                {Gravity.CENTER, horizontallyCentered, verticallyCentered},
+                {Gravity.CENTER_HORIZONTAL, horizontallyCentered, 0},
+                {Gravity.CENTER_VERTICAL, 0, verticallyCentered},
+                {Gravity.RIGHT, end, 0},
+                {GravityCompat.END, end, 0},
+                {Gravity.BOTTOM, 0, bottom},
+                {Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, horizontallyCentered, bottom},
+                {Gravity.RIGHT | Gravity.CENTER_VERTICAL, end, verticallyCentered},
+        };
+        for (final int[] testCase : testCases) {
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    final CoordinatorLayout.LayoutParams lp =
+                            (CoordinatorLayout.LayoutParams) view.getLayoutParams();
+                    lp.gravity = testCase[0];
+                    view.setLayoutParams(lp);
+                }
+            });
+            instrumentation.waitForIdleSync();
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    assertThat("Gravity: " + testCase[0], view.getLeft(), is(testCase[1]));
+                    assertThat("Gravity: " + testCase[0], view.getTop(), is(testCase[2]));
+                }
+            });
+        }
+    }
+
+    @Test
     public void testInsetDependency() {
         final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
 
diff --git a/fragment/java/android/support/v4/app/BackStackRecord.java b/fragment/java/android/support/v4/app/BackStackRecord.java
index e335b9f..5b992d6 100644
--- a/fragment/java/android/support/v4/app/BackStackRecord.java
+++ b/fragment/java/android/support/v4/app/BackStackRecord.java
@@ -816,7 +816,7 @@
                 default:
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
             }
-            if (!mAllowOptimization && op.cmd != OP_ADD && f != null) {
+            if (!mAllowOptimization && op.cmd != OP_REMOVE && f != null) {
                 mManager.moveFragmentToExpectedState(f);
             }
         }
@@ -916,6 +916,39 @@
         return oldPrimaryNav;
     }
 
+    /**
+     * Removes fragments that are added or removed during a pop operation.
+     *
+     * @param added Initialized to the fragments that are in the mManager.mAdded, this
+     *              will be modified to contain the fragments that will be in mAdded
+     *              after the execution ({@link #executeOps()}.
+     * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
+     *                      this set of ops
+     * @return the new oldPrimaryNav fragment after this record's ops would be popped
+     */
+    Fragment trackAddedFragmentsInPop(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
+        for (int opNum = 0; opNum < mOps.size(); opNum++) {
+            final Op op = mOps.get(opNum);
+            switch (op.cmd) {
+                case OP_ADD:
+                case OP_ATTACH:
+                    added.remove(op.fragment);
+                    break;
+                case OP_REMOVE:
+                case OP_DETACH:
+                    added.add(op.fragment);
+                    break;
+                case OP_UNSET_PRIMARY_NAV:
+                    oldPrimaryNav = op.fragment;
+                    break;
+                case OP_SET_PRIMARY_NAV:
+                    oldPrimaryNav = null;
+                    break;
+            }
+        }
+        return oldPrimaryNav;
+    }
+
     boolean isPostponed() {
         for (int opNum = 0; opNum < mOps.size(); opNum++) {
             final Op op = mOps.get(opNum);
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index daf56d4..10f15f6 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -2126,11 +2126,13 @@
                 if (startIndex != recordNum) {
                     executeOpsTogether(records, isRecordPop, startIndex, recordNum);
                 }
-                // execute all unoptimized together
-                int optimizeEnd;
-                for (optimizeEnd = recordNum + 1; optimizeEnd < numRecords; optimizeEnd++) {
-                    if (records.get(optimizeEnd).mAllowOptimization) {
-                        break;
+                // execute all unoptimized pop operations together or one add operation
+                int optimizeEnd = recordNum + 1;
+                if (isRecordPop.get(recordNum)) {
+                    while (optimizeEnd < numRecords
+                            && isRecordPop.get(optimizeEnd)
+                            && !records.get(optimizeEnd).mAllowOptimization) {
+                        optimizeEnd++;
                     }
                 }
                 executeOpsTogether(records, isRecordPop, recordNum, optimizeEnd);
@@ -2169,6 +2171,8 @@
             final boolean isPop = isRecordPop.get(recordNum);
             if (!isPop) {
                 oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
+            } else {
+                oldPrimaryNav = record.trackAddedFragmentsInPop(mTmpAddedFragments, oldPrimaryNav);
             }
             final int bumpAmount = isPop ? -1 : 1;
             record.bumpBackStackNesting(bumpAmount);
diff --git a/fragment/java/android/support/v4/app/FragmentTransition.java b/fragment/java/android/support/v4/app/FragmentTransition.java
index 03eba77..ff7f91e 100644
--- a/fragment/java/android/support/v4/app/FragmentTransition.java
+++ b/fragment/java/android/support/v4/app/FragmentTransition.java
@@ -188,7 +188,10 @@
     private static void configureTransitionsOptimized(FragmentManagerImpl fragmentManager,
             int containerId, FragmentContainerTransition fragments,
             View nonExistentView, ArrayMap<String, String> nameOverrides) {
-        ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        ViewGroup sceneRoot = null;
+        if (fragmentManager.mContainer.onHasView()) {
+            sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        }
         if (sceneRoot == null) {
             return;
         }
@@ -278,7 +281,10 @@
     private static void configureTransitionsUnoptimized(FragmentManagerImpl fragmentManager,
             int containerId, FragmentContainerTransition fragments,
             View nonExistentView, ArrayMap<String, String> nameOverrides) {
-        ViewGroup sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        ViewGroup sceneRoot = null;
+        if (fragmentManager.mContainer.onHasView()) {
+            sceneRoot = (ViewGroup) fragmentManager.mContainer.onFindViewById(containerId);
+        }
         if (sceneRoot == null) {
             return;
         }
diff --git a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
index ad970c8..521eb84 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentViewTests.java
@@ -32,6 +32,7 @@
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.app.test.FragmentTestActivity;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -902,6 +903,105 @@
         assertEquals(View.GONE, fragment2.getView().getVisibility());
     }
 
+    // Test to ensure that popping and adding a fragment properly track the fragments added
+    // and removed.
+    @Test
+    public void popAdd() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+        // One fragment with a view
+        final StrictViewFragment fragment1 = new StrictViewFragment();
+        fm.beginTransaction().add(R.id.fragmentContainer, fragment1).addToBackStack(null).commit();
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+        FragmentTestUtil.assertChildren(container, fragment1);
+
+        final StrictViewFragment fragment2 = new StrictViewFragment();
+        final StrictViewFragment fragment3 = new StrictViewFragment();
+        mInstrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                fm.popBackStack();
+                fm.beginTransaction()
+                        .replace(R.id.fragmentContainer, fragment2)
+                        .addToBackStack(null)
+                        .commit();
+                fm.executePendingTransactions();
+                fm.popBackStack();
+                fm.beginTransaction()
+                        .replace(R.id.fragmentContainer, fragment3)
+                        .addToBackStack(null)
+                        .commit();
+                fm.executePendingTransactions();
+            }
+        });
+        FragmentTestUtil.assertChildren(container, fragment3);
+    }
+
+    // Ensure that non-optimized transactions are executed individually rather than together.
+    // This forces references from one fragment to another that should be executed earlier
+    // to work.
+    @Test
+    public void nonOptimizeTogether() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+        final StrictViewFragment fragment1 = new StrictViewFragment();
+        fragment1.setLayoutId(R.layout.scene1);
+        final StrictViewFragment fragment2 = new StrictViewFragment();
+        fragment2.setLayoutId(R.layout.fragment_a);
+
+        mActivityRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                fm.beginTransaction()
+                        .add(R.id.fragmentContainer, fragment1)
+                        .setAllowOptimization(false)
+                        .addToBackStack(null)
+                        .commit();
+                fm.beginTransaction()
+                        .add(R.id.squareContainer, fragment2)
+                        .setAllowOptimization(false)
+                        .addToBackStack(null)
+                        .commit();
+                fm.executePendingTransactions();
+            }
+        });
+        FragmentTestUtil.assertChildren(container, fragment1);
+        assertNotNull(findViewById(R.id.textA));
+    }
+
+    // Ensure that there is no problem if the child fragment manager is used before
+    // the View has been added.
+    @Test
+    public void childFragmentManager() throws Throwable {
+        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
+        ViewGroup container = (ViewGroup)
+                mActivityRule.getActivity().findViewById(R.id.fragmentContainer);
+        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+
+        final StrictViewFragment fragment1 = new ParentFragment();
+        fragment1.setLayoutId(R.layout.double_container);
+
+        fm.beginTransaction()
+                .add(R.id.fragmentContainer, fragment1)
+                .addToBackStack(null)
+                .commit();
+
+        FragmentTestUtil.executePendingTransactions(mActivityRule);
+
+        FragmentTestUtil.assertChildren(container, fragment1);
+        ViewGroup innerContainer = (ViewGroup)
+                fragment1.getView().findViewById(R.id.fragmentContainer1);
+
+        Fragment fragment2 = fragment1.getChildFragmentManager().findFragmentByTag("inner");
+        FragmentTestUtil.assertChildren(innerContainer, fragment2);
+    }
+
     private View findViewById(int viewId) {
         return mActivityRule.getActivity().findViewById(viewId);
     }
@@ -925,4 +1025,25 @@
             super.onViewCreated(view, savedInstanceState);
         }
     }
+
+    public static class ParentFragment extends StrictViewFragment {
+        public ParentFragment() {
+            setLayoutId(R.layout.double_container);
+        }
+
+        @Override
+        public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                                 Bundle savedInstanceState) {
+            View view = super.onCreateView(inflater, container, savedInstanceState);
+            final StrictViewFragment fragment2 = new StrictViewFragment();
+            fragment2.setLayoutId(R.layout.fragment_a);
+
+            getChildFragmentManager().beginTransaction()
+                    .add(R.id.fragmentContainer1, fragment2, "inner")
+                    .addToBackStack(null)
+                    .commit();
+            getChildFragmentManager().executePendingTransactions();
+            return view;
+        }
+    }
 }
diff --git a/gradle.properties b/gradle.properties
index 34b3995..fb870a9 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,4 @@
 org.gradle.jvmargs=-Xmx3g
 org.gradle.daemon=true
-org.gradle.configureondemand=true
\ No newline at end of file
+org.gradle.configureondemand=true
+org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ccfc973..84939b4 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-3.2-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-3.3-bin.zip
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
index be0959d..c2fa6ab 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java
@@ -591,21 +591,21 @@
          * Called when the list of children is loaded or updated.
          *
          * @param parentId The media id of the parent media item.
-         * @param children The children which were loaded, or null if the id is invalid.
+         * @param children The children which were loaded.
          */
-        public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children) {
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children) {
         }
 
         /**
          * Called when the list of children is loaded or updated.
          *
          * @param parentId The media id of the parent media item.
-         * @param children The children which were loaded, or null if the id is invalid.
+         * @param children The children which were loaded.
          * @param options A bundle of service-specific arguments to send to the media
          *            browse service. The contents of this bundle may affect the
          *            information returned when browsing.
          */
-        public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children,
+        public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaItem> children,
                 @NonNull Bundle options) {
         }
 
@@ -979,13 +979,14 @@
                 sub = new Subscription();
                 mSubscriptions.put(parentId, sub);
             }
-            sub.putCallback(options, callback);
+            Bundle copiedOptions = options == null ? null : new Bundle(options);
+            sub.putCallback(copiedOptions, callback);
 
             // If we are connected, tell the service that we are watching. If we aren't
             // connected, the service will be told when we connect.
             if (mState == CONNECT_STATE_CONNECTED) {
                 try {
-                    mServiceBinderWrapper.addSubscription(parentId, callback.mToken, options,
+                    mServiceBinderWrapper.addSubscription(parentId, callback.mToken, copiedOptions,
                             mCallbacksMessenger);
                 } catch (RemoteException e) {
                     // Process is crashing. We will disconnect, and upon reconnect we will
@@ -1141,7 +1142,6 @@
                 return;
             }
 
-            List<MediaItem> data = list;
             if (DEBUG) {
                 Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
             }
@@ -1159,9 +1159,17 @@
             SubscriptionCallback subscriptionCallback = subscription.getCallback(options);
             if (subscriptionCallback != null) {
                 if (options == null) {
-                    subscriptionCallback.onChildrenLoaded(parentId, data);
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list);
+                    }
                 } else {
-                    subscriptionCallback.onChildrenLoaded(parentId, data, options);
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId, options);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
+                    }
                 }
             }
         }
@@ -1410,7 +1418,8 @@
                 mSubscriptions.put(parentId, sub);
             }
             callback.setSubscription(sub);
-            sub.putCallback(options, callback);
+            Bundle copiedOptions = options == null ? null : new Bundle(options);
+            sub.putCallback(copiedOptions, callback);
 
             if (mServiceBinderWrapper == null) {
                 MediaBrowserCompatApi21.subscribe(
@@ -1418,7 +1427,7 @@
             } else {
                 try {
                     mServiceBinderWrapper.addSubscription(
-                            parentId, callback.mToken, options, mCallbacksMessenger);
+                            parentId, callback.mToken, copiedOptions, mCallbacksMessenger);
                 } catch (RemoteException e) {
                     // Process is crashing. We will disconnect, and upon reconnect we will
                     // automatically reregister. So nothing to do here.
@@ -1584,9 +1593,17 @@
             SubscriptionCallback subscriptionCallback = subscription.getCallback(options);
             if (subscriptionCallback != null) {
                 if (options == null) {
-                    subscriptionCallback.onChildrenLoaded(parentId, list);
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list);
+                    }
                 } else {
-                    subscriptionCallback.onChildrenLoaded(parentId, list, options);
+                    if (list == null) {
+                        subscriptionCallback.onError(parentId, options);
+                    } else {
+                        subscriptionCallback.onChildrenLoaded(parentId, list, options);
+                    }
                 }
             }
         }
diff --git a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
index de5047b..77a6e19 100644
--- a/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/media-compat/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -842,11 +842,16 @@
      * {@link Result#detach result.detach} may be called before returning from
      * this function, and then {@link Result#sendResult result.sendResult}
      * called when the loading is complete.
+     * </p><p>
+     * In case the media item does not have any children, call {@link Result#sendResult}
+     * with an empty list. When the given {@code parentId} is invalid, implementations must
+     * call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
+     * {@link MediaBrowserCompat.SubscriptionCallback#onError}.
+     * </p>
      *
      * @param parentId The id of the parent media item whose children are to be
      *            queried.
-     * @param result The Result to send the list of children to, or null if the
-     *            id is invalid.
+     * @param result The Result to send the list of children to.
      */
     public abstract void onLoadChildren(@NonNull String parentId,
             @NonNull Result<List<MediaBrowserCompat.MediaItem>> result);
@@ -860,11 +865,16 @@
      * {@link Result#detach result.detach} may be called before returning from
      * this function, and then {@link Result#sendResult result.sendResult}
      * called when the loading is complete.
+     * </p><p>
+     * In case the media item does not have any children, call {@link Result#sendResult}
+     * with an empty list. When the given {@code parentId} is invalid, implementations must
+     * call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
+     * {@link MediaBrowserCompat.SubscriptionCallback#onError}.
+     * </p>
      *
      * @param parentId The id of the parent media item whose children are to be
      *            queried.
-     * @param result The Result to send the list of children to, or null if the
-     *            id is invalid.
+     * @param result The Result to send the list of children to.
      * @param options A bundle of service-specific arguments sent from the media
      *            browse. The information returned through the result should be
      *            affected by the contents of this bundle.
diff --git a/media-compat/tests/AndroidManifest.xml b/media-compat/tests/AndroidManifest.xml
index 3ee2993..1afbcdf 100644
--- a/media-compat/tests/AndroidManifest.xml
+++ b/media-compat/tests/AndroidManifest.xml
@@ -26,7 +26,6 @@
 
     <application android:supportsRtl="true">
         <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.support.v4.media.session.TestActivity" />
         <receiver android:name="android.support.v4.media.session.MediaButtonReceiver" >
             <intent-filter>
                 <action android:name="android.intent.action.MEDIA_BUTTON" />
diff --git a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
index 08c838c..f6669f7 100644
--- a/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/MediaBrowserCompatTest.java
@@ -175,7 +175,7 @@
         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
     }
 
-    // TODO(hdmoon): Uncomment after fixing failing tests. (Fails on API <= 25)
+    // TODO(hdmoon): Uncomment after fixing failing tests. (Fails on API 24, 25)
     // @Test
     // @SmallTest
     public void testSubscribeWithOptions() {
@@ -231,9 +231,8 @@
         assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
     }
 
-    // TODO(hdmoon): Uncomment after fixing failing tests. (Fails on API <= 23)
-    // @Test
-    // @SmallTest
+    @Test
+    @SmallTest
     public void testSubscribeInvalidItem() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -251,9 +250,8 @@
                 mSubscriptionCallback.mLastErrorId);
     }
 
-    // TODO(hdmoon): Uncomment after fixing failing tests. (Fails on API <= 23)
-    // @Test
-    // @SmallTest
+    @Test
+    @SmallTest
     public void testSubscribeInvalidItemWithOptions() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
@@ -392,9 +390,8 @@
         }
     }
 
-    // TODO(hdmoon): Uncomment after fixing failing tests. (Fails on API >= 24)
-    // @Test
-    // @SmallTest
+    @Test
+    @SmallTest
     public void testGetItem() {
         resetCallbacks();
         createMediaBrowser(TEST_BROWSER_SERVICE);
diff --git a/media-compat/tests/src/android/support/v4/media/MediaItemTest.java b/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
new file mode 100644
index 0000000..bd2565f
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/MediaItemTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.v4.media.MediaBrowserCompat.MediaItem;
+
+import org.junit.Test;
+
+/**
+ * Test {@link MediaBrowserCompat.MediaItem}.
+ */
+public class MediaItemTest {
+    private static final String DESCRIPTION = "test_description";
+    private static final String MEDIA_ID = "test_media_id";
+    private static final String TITLE = "test_title";
+    private static final String SUBTITLE = "test_subtitle";
+
+    @Test
+    @SmallTest
+    public void testBrowsableMediaItem() {
+        MediaDescriptionCompat description =
+                new MediaDescriptionCompat.Builder()
+                        .setDescription(DESCRIPTION)
+                        .setMediaId(MEDIA_ID)
+                        .setTitle(TITLE)
+                        .setSubtitle(SUBTITLE)
+                        .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
+        assertTrue(mediaItem.isBrowsable());
+        assertFalse(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+
+    @Test
+    @SmallTest
+    public void testPlayableMediaItem() {
+        MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
+                .setDescription(DESCRIPTION)
+                .setMediaId(MEDIA_ID)
+                .setTitle(TITLE)
+                .setSubtitle(SUBTITLE)
+                .build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
+        assertFalse(mediaItem.isBrowsable());
+        assertTrue(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        assertEquals(mediaItem.getFlags(), p.readInt());
+        assertEquals(
+                description.toString(),
+                MediaDescriptionCompat.CREATOR.createFromParcel(p).toString());
+        p.recycle();
+    }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
new file mode 100644
index 0000000..8151fae
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaControllerCompatTest.java
@@ -0,0 +1,596 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media.session;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link MediaControllerCompat}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MediaControllerCompatTest {
+    // The maximum time to wait for an operation.
+    private static final long TIME_OUT_MS = 3000L;
+    private static final String SESSION_TAG = "test-session";
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-val";
+    private static final float DELTA = 1e-4f;
+
+    private final Object mWaitLock = new Object();
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private MediaSessionCompat mSession;
+    private MediaSessionCallback mCallback = new MediaSessionCallback();
+    private MediaControllerCompat mController;
+
+    @Before
+    public void setUp() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mSession = new MediaSessionCompat(getContext(), SESSION_TAG);
+                mSession.setCallback(mCallback, mHandler);
+                mController = mSession.getController();
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSession.release();
+    }
+
+    @Test
+    @SmallTest
+    public void testGetPackageName() {
+        assertEquals(getContext().getPackageName(), mController.getPackageName());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetRatingType() {
+        assertEquals("Default rating type of a session must be RatingCompat.RATING_NONE",
+                RatingCompat.RATING_NONE, mController.getRatingType());
+
+        mSession.setRatingType(RatingCompat.RATING_5_STARS);
+        assertEquals(RatingCompat.RATING_5_STARS, mController.getRatingType());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetSessionToken() throws Exception {
+        assertEquals(mSession.getSessionToken(), mController.getSessionToken());
+    }
+
+    @Test
+    @SmallTest
+    public void testSendCommand() throws Exception {
+        synchronized (mWaitLock) {
+            mCallback.reset();
+            final String command = "test-command";
+            final Bundle extras = new Bundle();
+            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+            mController.sendCommand(command, extras, new ResultReceiver(null));
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCommandCalled);
+            assertNotNull(mCallback.mCommandCallback);
+            assertEquals(command, mCallback.mCommand);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+        }
+    }
+
+    // TODO(hdmoon): Uncomment after fixing this test. This test causes an Exception on System UI.
+    // @Test
+    // @SmallTest
+    public void testVolumeControl() throws Exception {
+        VolumeProviderCompat vp =
+                new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
+            @Override
+            public void onSetVolumeTo(int volume) {
+                synchronized (mWaitLock) {
+                    setCurrentVolume(volume);
+                    mWaitLock.notify();
+                }
+            }
+
+            @Override
+            public void onAdjustVolume(int direction) {
+                synchronized (mWaitLock) {
+                    switch (direction) {
+                        case AudioManager.ADJUST_LOWER:
+                            setCurrentVolume(getCurrentVolume() - 1);
+                            break;
+                        case AudioManager.ADJUST_RAISE:
+                            setCurrentVolume(getCurrentVolume() + 1);
+                            break;
+                    }
+                    mWaitLock.notify();
+                }
+            }
+        };
+        mSession.setPlaybackToRemote(vp);
+
+        synchronized (mWaitLock) {
+            // test setVolumeTo
+            mController.setVolumeTo(7, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+
+            // test adjustVolume
+            mController.adjustVolume(AudioManager.ADJUST_LOWER, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(6, vp.getCurrentVolume());
+
+            mController.adjustVolume(AudioManager.ADJUST_RAISE, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testTransportControlsAndMediaSessionCallback() throws Exception {
+        MediaControllerCompat.TransportControls controls = mController.getTransportControls();
+        synchronized (mWaitLock) {
+            mCallback.reset();
+            controls.play();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayCalled);
+
+            mCallback.reset();
+            controls.pause();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPauseCalled);
+
+            mCallback.reset();
+            controls.stop();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnStopCalled);
+
+            mCallback.reset();
+            controls.fastForward();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnFastForwardCalled);
+
+            mCallback.reset();
+            controls.rewind();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnRewindCalled);
+
+            mCallback.reset();
+            controls.skipToPrevious();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToPreviousCalled);
+
+            mCallback.reset();
+            controls.skipToNext();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToNextCalled);
+
+            mCallback.reset();
+            final long seekPosition = 1000;
+            controls.seekTo(seekPosition);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSeekToCalled);
+            assertEquals(seekPosition, mCallback.mSeekPosition);
+
+            mCallback.reset();
+            final RatingCompat rating =
+                    RatingCompat.newStarRating(RatingCompat.RATING_5_STARS, 3f);
+            controls.setRating(rating);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSetRatingCalled);
+            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
+            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating(), DELTA);
+
+            mCallback.reset();
+            final String mediaId = "test-media-id";
+            final Bundle extras = new Bundle();
+            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+            controls.playFromMediaId(mediaId, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromMediaIdCalled);
+            assertEquals(mediaId, mCallback.mMediaId);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final String query = "test-query";
+            controls.playFromSearch(query, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromSearchCalled);
+            assertEquals(query, mCallback.mQuery);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final Uri uri = Uri.parse("content://test/popcorn.mod");
+            controls.playFromUri(uri, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromUriCalled);
+            assertEquals(uri, mCallback.mUri);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final String action = "test-action";
+            controls.sendCustomAction(action, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCustomActionCalled);
+            assertEquals(action, mCallback.mAction);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            mCallback.mOnCustomActionCalled = false;
+            final PlaybackStateCompat.CustomAction customAction =
+                    new PlaybackStateCompat.CustomAction.Builder(action, action, -1)
+                            .setExtras(extras)
+                            .build();
+            controls.sendCustomAction(customAction, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCustomActionCalled);
+            assertEquals(action, mCallback.mAction);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final long queueItemId = 1000;
+            controls.skipToQueueItem(queueItemId);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToQueueItemCalled);
+            assertEquals(queueItemId, mCallback.mQueueItemId);
+
+            mCallback.reset();
+            controls.prepare();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareCalled);
+
+            mCallback.reset();
+            controls.prepareFromMediaId(mediaId, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
+            assertEquals(mediaId, mCallback.mMediaId);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            controls.prepareFromSearch(query, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromSearchCalled);
+            assertEquals(query, mCallback.mQuery);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            controls.prepareFromUri(uri, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromUriCalled);
+            assertEquals(uri, mCallback.mUri);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+
+            mCallback.reset();
+            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+            controls.setRepeatMode(repeatMode);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSetRepeatModeCalled);
+            assertEquals(repeatMode, mCallback.mRepeatMode);
+
+            mCallback.reset();
+            final boolean shuffleModeEnabled = true;
+            controls.setShuffleModeEnabled(shuffleModeEnabled);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSetShuffleModeEnabledCalled);
+            assertEquals(shuffleModeEnabled, mCallback.mShuffleModeEnabled);
+        }
+    }
+
+    @Test
+    @SmallTest
+    public void testPlaybackInfo() {
+        final int playbackType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+        final int volumeControl = VolumeProviderCompat.VOLUME_CONTROL_ABSOLUTE;
+        final int maxVolume = 10;
+        final int currentVolume = 3;
+
+        int audioStream = 77;
+        MediaControllerCompat.PlaybackInfo info = new MediaControllerCompat.PlaybackInfo(
+                playbackType, audioStream, volumeControl, maxVolume, currentVolume);
+
+        assertEquals(playbackType, info.getPlaybackType());
+        assertEquals(audioStream, info.getAudioStream());
+        assertEquals(volumeControl, info.getVolumeControl());
+        assertEquals(maxVolume, info.getMaxVolume());
+        assertEquals(currentVolume, info.getCurrentVolume());
+    }
+
+    private class MediaSessionCallback extends MediaSessionCompat.Callback {
+        private long mSeekPosition;
+        private long mQueueItemId;
+        private RatingCompat mRating;
+        private String mMediaId;
+        private String mQuery;
+        private Uri mUri;
+        private String mAction;
+        private String mCommand;
+        private Bundle mExtras;
+        private ResultReceiver mCommandCallback;
+        private int mRepeatMode;
+        private boolean mShuffleModeEnabled;
+
+        private boolean mOnPlayCalled;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
+        private boolean mOnSeekToCalled;
+        private boolean mOnSkipToQueueItemCalled;
+        private boolean mOnSetRatingCalled;
+        private boolean mOnPlayFromMediaIdCalled;
+        private boolean mOnPlayFromSearchCalled;
+        private boolean mOnPlayFromUriCalled;
+        private boolean mOnCustomActionCalled;
+        private boolean mOnCommandCalled;
+        private boolean mOnPrepareCalled;
+        private boolean mOnPrepareFromMediaIdCalled;
+        private boolean mOnPrepareFromSearchCalled;
+        private boolean mOnPrepareFromUriCalled;
+        private boolean mOnSetRepeatModeCalled;
+        private boolean mOnSetShuffleModeEnabledCalled;
+
+        public void reset() {
+            mSeekPosition = -1;
+            mQueueItemId = -1;
+            mRating = null;
+            mMediaId = null;
+            mQuery = null;
+            mUri = null;
+            mAction = null;
+            mExtras = null;
+            mCommand = null;
+            mCommandCallback = null;
+            mShuffleModeEnabled = false;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+
+            mOnPlayCalled = false;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
+            mOnSkipToQueueItemCalled = false;
+            mOnSeekToCalled = false;
+            mOnSetRatingCalled = false;
+            mOnPlayFromMediaIdCalled = false;
+            mOnPlayFromSearchCalled = false;
+            mOnPlayFromUriCalled = false;
+            mOnCustomActionCalled = false;
+            mOnCommandCalled = false;
+            mOnPrepareCalled = false;
+            mOnPrepareFromMediaIdCalled = false;
+            mOnPrepareFromSearchCalled = false;
+            mOnPrepareFromUriCalled = false;
+            mOnSetRepeatModeCalled = false;
+            mOnSetShuffleModeEnabledCalled = false;
+        }
+
+        @Override
+        public void onPlay() {
+            synchronized (mWaitLock) {
+                mOnPlayCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPause() {
+            synchronized (mWaitLock) {
+                mOnPauseCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onStop() {
+            synchronized (mWaitLock) {
+                mOnStopCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onFastForward() {
+            synchronized (mWaitLock) {
+                mOnFastForwardCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRewind() {
+            synchronized (mWaitLock) {
+                mOnRewindCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            synchronized (mWaitLock) {
+                mOnSkipToPreviousCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToNext() {
+            synchronized (mWaitLock) {
+                mOnSkipToNextCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            synchronized (mWaitLock) {
+                mOnSeekToCalled = true;
+                mSeekPosition = pos;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSetRating(RatingCompat rating) {
+            synchronized (mWaitLock) {
+                mOnSetRatingCalled = true;
+                mRating = rating;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromMediaIdCalled = true;
+                mMediaId = mediaId;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromSearchCalled = true;
+                mQuery = query;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromUri(Uri uri, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromUriCalled = true;
+                mUri = uri;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCustomAction(String action, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnCustomActionCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToQueueItem(long id) {
+            synchronized (mWaitLock) {
+                mOnSkipToQueueItemCalled = true;
+                mQueueItemId = id;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+            synchronized (mWaitLock) {
+                mOnCommandCalled = true;
+                mCommand = command;
+                mExtras = extras;
+                mCommandCallback = cb;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepare() {
+            synchronized (mWaitLock) {
+                mOnPrepareCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromMediaIdCalled = true;
+                mMediaId = mediaId;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromSearchCalled = true;
+                mQuery = query;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromUriCalled = true;
+                mUri = uri;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSetRepeatMode(int repeatMode) {
+            synchronized (mWaitLock) {
+                mOnSetRepeatModeCalled = true;
+                mRepeatMode = repeatMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSetShuffleModeEnabled(boolean enabled) {
+            synchronized (mWaitLock) {
+                mOnSetShuffleModeEnabledCalled = true;
+                mShuffleModeEnabled = enabled;
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
index 72460f5..a5f11ed 100644
--- a/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
+++ b/media-compat/tests/src/android/support/v4/media/session/MediaSessionCompatTest.java
@@ -16,85 +16,701 @@
 
 package android.support.v4.media.session;
 
-import static junit.framework.Assert.fail;
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
 import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.rule.ActivityTestRule;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.RatingCompat;
+import android.support.v4.media.VolumeProviderCompat;
+import android.view.KeyEvent;
 
+import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
+import java.util.ArrayList;
+import java.util.List;
 
+/**
+ * Test {@link MediaSessionCompat}.
+ */
 @RunWith(AndroidJUnit4.class)
 public class MediaSessionCompatTest {
-    @Rule
-    public ActivityTestRule<TestActivity> mActivityRule =
-            new ActivityTestRule<>(TestActivity.class);
-    Context mContext;
-    Map<String, LockedObject> results = new HashMap<>();
+    // The maximum time to wait for an operation.
+    private static final long TIME_OUT_MS = 3000L;
+    private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
+    private static final String TEST_SESSION_TAG = "test-session-tag";
+    private static final String TEST_KEY = "test-key";
+    private static final String TEST_VALUE = "test-val";
+    private static final String TEST_SESSION_EVENT = "test-session-event";
+    private static final int TEST_CURRENT_VOLUME = 10;
+    private static final int TEST_MAX_VOLUME = 11;
+    private static final long TEST_QUEUE_ID = 12L;
+    private static final long TEST_ACTION = 55L;
+
+    private AudioManager mAudioManager;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Object mWaitLock = new Object();
+    private MediaControllerCallback mCallback = new MediaControllerCallback();
+    private MediaSessionCompat mSession;
 
     @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getContext();
+    public void setUp() throws Exception {
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+                mSession = new MediaSessionCompat(getContext(), TEST_SESSION_TAG);
+            }
+        });
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // It is OK to call release() twice.
+        mSession.release();
+    }
+
+    /**
+     * Tests that a session can be created and that all the fields are
+     * initialized correctly.
+     */
+    @Test
+    @SmallTest
+    public void testCreateSession() throws Exception {
+        assertNotNull(mSession.getSessionToken());
+        assertFalse("New session should not be active", mSession.isActive());
+
+        // Verify by getting the controller and checking all its fields
+        MediaControllerCompat controller = mSession.getController();
+        assertNotNull(controller);
+        verifyNewSession(controller, TEST_SESSION_TAG);
+    }
+
+    /**
+     * Tests MediaSessionCompat.Token created in the constructor of MediaSessionCompat.
+     */
+    @Test
+    @SmallTest
+    public void testSessionToken() throws Exception {
+        MediaSessionCompat.Token sessionToken = mSession.getSessionToken();
+
+        assertNotNull(sessionToken);
+        assertEquals(0, sessionToken.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        sessionToken.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.Token token = MediaSessionCompat.Token.CREATOR.createFromParcel(p);
+        assertEquals(token, sessionToken);
+        p.recycle();
+    }
+
+    /**
+     * Tests that the various configuration bits on a session get passed to the
+     * controller.
+     */
+    @Test
+    @SmallTest
+    public void testConfigureSession() throws Exception {
+        MediaControllerCompat controller = mSession.getController();
+        controller.registerCallback(mCallback, mHandler);
+
+        synchronized (mWaitLock) {
+            // test setExtras
+            mCallback.resetLocked();
+            final Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            mSession.setExtras(extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnExtraChangedCalled);
+
+            Bundle extrasOut = mCallback.mExtras;
+            assertNotNull(extrasOut);
+            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
+
+            extrasOut = controller.getExtras();
+            assertNotNull(extrasOut);
+            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
+
+            // test setFlags
+            mSession.setFlags(5);
+            assertEquals(5, controller.getFlags());
+
+            // test setMetadata
+            mCallback.resetLocked();
+            MediaMetadataCompat metadata =
+                    new MediaMetadataCompat.Builder().putString(TEST_KEY, TEST_VALUE).build();
+            mSession.setMetadata(metadata);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnMetadataChangedCalled);
+
+            MediaMetadataCompat metadataOut = mCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            metadataOut = controller.getMetadata();
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            // test setPlaybackState
+            mCallback.resetLocked();
+            PlaybackStateCompat state =
+                    new PlaybackStateCompat.Builder().setActions(TEST_ACTION).build();
+            mSession.setPlaybackState(state);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlaybackStateChangedCalled);
+
+            PlaybackStateCompat stateOut = mCallback.mPlaybackState;
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+
+            stateOut = controller.getPlaybackState();
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+
+            // test setQueue and setQueueTitle
+            mCallback.resetLocked();
+            List<MediaSessionCompat.QueueItem> queue = new ArrayList<>();
+            MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
+                    new MediaDescriptionCompat.Builder()
+                            .setMediaId(TEST_VALUE)
+                            .setTitle("title")
+                            .build(),
+                    TEST_QUEUE_ID);
+            queue.add(item);
+            mSession.setQueue(queue);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueChangedCalled);
+
+            mSession.setQueueTitle(TEST_VALUE);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueTitleChangedCalled);
+
+            assertEquals(TEST_VALUE, mCallback.mTitle);
+            assertEquals(queue.size(), mCallback.mQueue.size());
+            assertEquals(TEST_QUEUE_ID, mCallback.mQueue.get(0).getQueueId());
+            assertEquals(TEST_VALUE, mCallback.mQueue.get(0).getDescription().getMediaId());
+
+            assertEquals(TEST_VALUE, controller.getQueueTitle());
+            assertEquals(queue.size(), controller.getQueue().size());
+            assertEquals(TEST_QUEUE_ID, controller.getQueue().get(0).getQueueId());
+            assertEquals(TEST_VALUE, controller.getQueue().get(0).getDescription().getMediaId());
+
+            mCallback.resetLocked();
+            mSession.setQueue(null);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueChangedCalled);
+
+            mSession.setQueueTitle(null);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueTitleChangedCalled);
+
+            assertNull(mCallback.mTitle);
+            assertNull(mCallback.mQueue);
+            assertNull(controller.getQueueTitle());
+            assertNull(controller.getQueue());
+
+            // test setSessionActivity
+            Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
+            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent, 0);
+            mSession.setSessionActivity(pi);
+            assertEquals(pi, controller.getSessionActivity());
+
+            // test setRepeatMode
+            mCallback.resetLocked();
+            final int repeatMode = PlaybackStateCompat.REPEAT_MODE_ALL;
+            mSession.setRepeatMode(repeatMode);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnRepeatModeChangedCalled);
+            assertEquals(repeatMode, mCallback.mRepeatMode);
+            assertEquals(repeatMode, controller.getRepeatMode());
+
+            // test setShuffleModeEnabled
+            mCallback.resetLocked();
+            final boolean shuffleModeEnabled = true;
+            mSession.setShuffleModeEnabled(shuffleModeEnabled);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnShuffleModeChangedCalled);
+            assertEquals(shuffleModeEnabled, mCallback.mShuffleModeEnabled);
+            assertEquals(shuffleModeEnabled, controller.isShuffleModeEnabled());
+
+            // test setActivity
+            mSession.setActive(true);
+            assertTrue(mSession.isActive());
+
+            // test sendSessionEvent
+            mCallback.resetLocked();
+            mSession.sendSessionEvent(TEST_SESSION_EVENT, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCallback.mOnSessionEventCalled);
+            assertEquals(TEST_SESSION_EVENT, mCallback.mEvent);
+            assertEquals(TEST_VALUE, mCallback.mExtras.getString(TEST_KEY));
+
+            // test release
+            mCallback.resetLocked();
+            mSession.release();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSessionDestroyedCalled);
+        }
+    }
+
+    /**
+     * Test {@link MediaSessionCompat#setPlaybackToLocal} and
+     * {@link MediaSessionCompat#setPlaybackToRemote}.
+     */
+    @Test
+    @SmallTest
+    public void testPlaybackToLocalAndRemote() throws Exception {
+        MediaControllerCompat controller = mSession.getController();
+        controller.registerCallback(mCallback, mHandler);
+
+        synchronized (mWaitLock) {
+            // test setPlaybackToRemote, do this before testing setPlaybackToLocal
+            // to ensure it switches correctly.
+            mCallback.resetLocked();
+            try {
+                mSession.setPlaybackToRemote(null);
+                fail("Expected IAE for setPlaybackToRemote(null)");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            VolumeProviderCompat vp = new VolumeProviderCompat(
+                    VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    TEST_MAX_VOLUME,
+                    TEST_CURRENT_VOLUME) {};
+            mSession.setPlaybackToRemote(vp);
+
+            MediaControllerCompat.PlaybackInfo info = null;
+            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
+                mCallback.mOnAudioInfoChangedCalled = false;
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mCallback.mOnAudioInfoChangedCalled);
+                info = mCallback.mPlaybackInfo;
+                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
+                        && info.getMaxVolume() == TEST_MAX_VOLUME
+                        && info.getVolumeControl() == VolumeProviderCompat.VOLUME_CONTROL_FIXED
+                        && info.getPlaybackType()
+                        == MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
+                    break;
+                }
+            }
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED,
+                    info.getVolumeControl());
+
+            info = controller.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_REMOTE,
+                    info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProviderCompat.VOLUME_CONTROL_FIXED, info.getVolumeControl());
+
+            // test setPlaybackToLocal
+            mSession.setPlaybackToLocal(AudioManager.STREAM_RING);
+            info = controller.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                    info.getPlaybackType());
+        }
+    }
+
+    /**
+     * Test {@link MediaSessionCompat.Callback#onMediaButtonEvent}.
+     */
+    @Test
+    @SmallTest
+    public void testCallbackOnMediaButtonEvent() throws Exception {
+        MediaSessionCallback sessionCallback = new MediaSessionCallback();
+        mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper()));
+        mSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
+        mSession.setActive(true);
+
+        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON).setComponent(
+                new ComponentName(getContext(), getContext().getClass()));
+        PendingIntent pi = PendingIntent.getBroadcast(getContext(), 0, mediaButtonIntent, 0);
+        mSession.setMediaButtonReceiver(pi);
+
+        long supportedActions = PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PAUSE
+                | PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_STOP
+                | PlaybackStateCompat.ACTION_SKIP_TO_NEXT
+                | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackStateCompat.ACTION_FAST_FORWARD | PlaybackStateCompat.ACTION_REWIND;
+
+        // Set state to STATE_PLAYING to get higher priority.
+        PlaybackStateCompat defaultState = new PlaybackStateCompat.Builder()
+                .setActions(supportedActions)
+                .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f)
+                .build();
+        mSession.setPlaybackState(defaultState);
+
+        synchronized (mWaitLock) {
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnPlayCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PAUSE);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnPauseCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_NEXT);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnSkipToNextCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnSkipToPreviousCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_STOP);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnStopCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnFastForwardCalled);
+
+            sessionCallback.reset();
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_REWIND);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnRewindCalled);
+
+            // Test PLAY_PAUSE button twice.
+            // First, send PLAY_PAUSE button event while in STATE_PAUSED.
+            sessionCallback.reset();
+            mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
+                    .setState(PlaybackStateCompat.STATE_PAUSED, 0L, 0.0f).build());
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnPlayCalled);
+
+            // Next, send PLAY_PAUSE button event while in STATE_PLAYING.
+            sessionCallback.reset();
+            mSession.setPlaybackState(new PlaybackStateCompat.Builder().setActions(supportedActions)
+                    .setState(PlaybackStateCompat.STATE_PLAYING, 0L, 0.0f).build());
+            sendMediaKeyInputToController(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(sessionCallback.mOnPauseCalled);
+        }
     }
 
     @Test
+    @SmallTest
     public void testSetNullCallback() throws Throwable {
-        initWait("testSetNullCallback");
-        mActivityRule.runOnUiThread(new Runnable() {
+        getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
                 try {
-                    MediaSessionCompat session = new MediaSessionCompat(mContext, "TEST");
+                    MediaSessionCompat session = new MediaSessionCompat(getContext(), "TEST");
                     session.setCallback(null);
                 } catch (Exception e) {
                     fail("Fail with an exception: " + e);
-                } finally {
-                    setResultData("testSetNullCallback", true);
                 }
             }
         });
-        waitFor("testSetNullCallback");
     }
 
-    private void initWait(String key) throws InterruptedException {
-        results.put(key, new LockedObject());
+    /**
+     * Tests {@link MediaSessionCompat.QueueItem}.
+     */
+    @Test
+    @SmallTest
+    public void testQueueItem() {
+        MediaSessionCompat.QueueItem item = new MediaSessionCompat.QueueItem(
+                new MediaDescriptionCompat.Builder()
+                        .setMediaId("media-id")
+                        .setTitle("title")
+                        .build(),
+                TEST_QUEUE_ID);
+        assertEquals(TEST_QUEUE_ID, item.getQueueId());
+        assertEquals("media-id", item.getDescription().getMediaId());
+        assertEquals("title", item.getDescription().getTitle());
+        assertEquals(0, item.describeContents());
+
+        Parcel p = Parcel.obtain();
+        item.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSessionCompat.QueueItem other =
+                MediaSessionCompat.QueueItem.CREATOR.createFromParcel(p);
+        assertEquals(item.toString(), other.toString());
+        p.recycle();
     }
 
-    private Object[] waitFor(String key) throws InterruptedException {
-        return results.get(key).waitFor();
+    /**
+     * Verifies that a new session hasn't had any configuration bits set yet.
+     *
+     * @param controller The controller for the session
+     */
+    private void verifyNewSession(MediaControllerCompat controller, String tag) {
+        assertEquals("New session has unexpected configuration", 0L, controller.getFlags());
+        assertNull("New session has unexpected configuration", controller.getExtras());
+        assertNull("New session has unexpected configuration", controller.getMetadata());
+        assertEquals("New session has unexpected configuration",
+                getContext().getPackageName(), controller.getPackageName());
+        assertNull("New session has unexpected configuration", controller.getPlaybackState());
+        assertNull("New session has unexpected configuration", controller.getQueue());
+        assertNull("New session has unexpected configuration", controller.getQueueTitle());
+        assertEquals("New session has unexpected configuration", RatingCompat.RATING_NONE,
+                controller.getRatingType());
+        assertNull("New session has unexpected configuration", controller.getSessionActivity());
+
+        assertNotNull(controller.getSessionToken());
+        assertNotNull(controller.getTransportControls());
+
+        MediaControllerCompat.PlaybackInfo info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        assertEquals(MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL,
+                info.getPlaybackType());
+        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
+                info.getCurrentVolume());
     }
 
-    private void setResultData(String key, Object... args) {
-        if (results.containsKey(key)) {
-            results.get(key).set(args);
+    private void sendMediaKeyInputToController(int keyCode) {
+        MediaControllerCompat controller = mSession.getController();
+        controller.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyCode));
+        controller.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, keyCode));
+    }
+
+    private class MediaControllerCallback extends MediaControllerCompat.Callback {
+        private volatile boolean mOnPlaybackStateChangedCalled;
+        private volatile boolean mOnMetadataChangedCalled;
+        private volatile boolean mOnQueueChangedCalled;
+        private volatile boolean mOnQueueTitleChangedCalled;
+        private volatile boolean mOnExtraChangedCalled;
+        private volatile boolean mOnAudioInfoChangedCalled;
+        private volatile boolean mOnSessionDestroyedCalled;
+        private volatile boolean mOnSessionEventCalled;
+        private volatile boolean mOnRepeatModeChangedCalled;
+        private volatile boolean mOnShuffleModeChangedCalled;
+
+        private volatile PlaybackStateCompat mPlaybackState;
+        private volatile MediaMetadataCompat mMediaMetadata;
+        private volatile List<MediaSessionCompat.QueueItem> mQueue;
+        private volatile CharSequence mTitle;
+        private volatile String mEvent;
+        private volatile Bundle mExtras;
+        private volatile MediaControllerCompat.PlaybackInfo mPlaybackInfo;
+        private volatile int mRepeatMode;
+        private volatile boolean mShuffleModeEnabled;
+
+        public void resetLocked() {
+            mOnPlaybackStateChangedCalled = false;
+            mOnMetadataChangedCalled = false;
+            mOnQueueChangedCalled = false;
+            mOnQueueTitleChangedCalled = false;
+            mOnExtraChangedCalled = false;
+            mOnAudioInfoChangedCalled = false;
+            mOnSessionDestroyedCalled = false;
+            mOnSessionEventCalled = false;
+            mOnRepeatModeChangedCalled = false;
+            mOnShuffleModeChangedCalled = false;
+
+            mPlaybackState = null;
+            mMediaMetadata = null;
+            mQueue = null;
+            mTitle = null;
+            mExtras = null;
+            mPlaybackInfo = null;
+            mRepeatMode = PlaybackStateCompat.REPEAT_MODE_NONE;
+            mShuffleModeEnabled = false;
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackStateCompat state) {
+            synchronized (mWaitLock) {
+                mOnPlaybackStateChangedCalled = true;
+                mPlaybackState = state;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadataCompat metadata) {
+            synchronized (mWaitLock) {
+                mOnMetadataChangedCalled = true;
+                mMediaMetadata = metadata;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
+            synchronized (mWaitLock) {
+                mOnQueueChangedCalled = true;
+                mQueue = queue;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            synchronized (mWaitLock) {
+                mOnQueueTitleChangedCalled = true;
+                mTitle = title;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnExtraChangedCalled = true;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onAudioInfoChanged(MediaControllerCompat.PlaybackInfo info) {
+            synchronized (mWaitLock) {
+                mOnAudioInfoChangedCalled = true;
+                mPlaybackInfo = info;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            synchronized (mWaitLock) {
+                mOnSessionDestroyedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSessionEventCalled = true;
+                mEvent = event;
+                mExtras = (Bundle) extras.clone();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRepeatModeChanged(int repeatMode) {
+            synchronized (mWaitLock) {
+                mOnRepeatModeChangedCalled = true;
+                mRepeatMode = repeatMode;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onShuffleModeChanged(boolean enabled) {
+            synchronized (mWaitLock) {
+                mOnShuffleModeChangedCalled = true;
+                mShuffleModeEnabled = enabled;
+                mWaitLock.notify();
+            }
         }
     }
 
-    private class LockedObject {
-        private Semaphore mLock = new Semaphore(1);
-        private volatile Object[] mArgs;
+    private class MediaSessionCallback extends MediaSessionCompat.Callback {
+        private boolean mOnPlayCalled;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
 
-        public LockedObject() {
-            mLock.drainPermits();
+        public void reset() {
+            mOnPlayCalled = false;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
         }
 
-        public void set(Object... args) {
-            mArgs = args;
-            mLock.release(1);
+        @Override
+        public void onPlay() {
+            synchronized (mWaitLock) {
+                mOnPlayCalled = true;
+                mWaitLock.notify();
+            }
         }
 
-        public Object[] waitFor() throws InterruptedException {
-            mLock.tryAcquire(1, 2, TimeUnit.SECONDS);
-            return mArgs;
+        @Override
+        public void onPause() {
+            synchronized (mWaitLock) {
+                mOnPauseCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onStop() {
+            synchronized (mWaitLock) {
+                mOnStopCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onFastForward() {
+            synchronized (mWaitLock) {
+                mOnFastForwardCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRewind() {
+            synchronized (mWaitLock) {
+                mOnRewindCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            synchronized (mWaitLock) {
+                mOnSkipToPreviousCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToNext() {
+            synchronized (mWaitLock) {
+                mOnSkipToNextCalled = true;
+                mWaitLock.notify();
+            }
         }
     }
 }
diff --git a/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java b/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java
new file mode 100644
index 0000000..410ae01
--- /dev/null
+++ b/media-compat/tests/src/android/support/v4/media/session/PlaybackStateCompatTest.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2016 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.support.v4.media.session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Test {@link PlaybackStateCompat}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PlaybackStateCompatTest {
+
+    private static final long TEST_POSITION = 20000L;
+    private static final long TEST_BUFFERED_POSITION = 15000L;
+    private static final long TEST_UPDATE_TIME = 100000L;
+    private static final long TEST_ACTIONS = PlaybackStateCompat.ACTION_PLAY
+            | PlaybackStateCompat.ACTION_STOP | PlaybackStateCompat.ACTION_SEEK_TO;
+    private static final long TEST_QUEUE_ITEM_ID = 23L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
+    private static final float DELTA = 1e-7f;
+
+    private static final String TEST_ERROR_MSG = "test-error-msg";
+    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
+    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
+    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
+
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-value";
+
+    /**
+     * Test default values of {@link PlaybackStateCompat}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder().build();
+
+        assertEquals(new ArrayList<PlaybackStateCompat.CustomAction>(), state.getCustomActions());
+        assertEquals(0, state.getState());
+        assertEquals(0L, state.getPosition());
+        assertEquals(0L, state.getBufferedPosition());
+        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
+        assertEquals(0L, state.getActions());
+        assertNull(state.getErrorMessage());
+        assertEquals(0L, state.getLastPositionUpdateTime());
+        assertEquals(MediaSessionCompat.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
+        assertNull(state.getExtras());
+    }
+
+    /**
+     * Test following setter methods of {@link PlaybackStateCompat.Builder}:
+     * {@link PlaybackStateCompat.Builder#setState(int, long, float)}
+     * {@link PlaybackStateCompat.Builder#setActions(long)}
+     * {@link PlaybackStateCompat.Builder#setActiveQueueItemId(long)}
+     * {@link PlaybackStateCompat.Builder#setBufferedPosition(long)}
+     * {@link PlaybackStateCompat.Builder#setErrorMessage(CharSequence)}
+     * {@link PlaybackStateCompat.Builder#setExtras(Bundle)}
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setterMethods() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(PlaybackStateCompat.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
+                .setActions(TEST_ACTIONS)
+                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                .setBufferedPosition(TEST_BUFFERED_POSITION)
+                .setErrorMessage(TEST_ERROR_MSG)
+                .setExtras(extras)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_PLAYING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_ACTIONS, state.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
+        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
+        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
+        assertNotNull(state.getExtras());
+        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#setState(int, long, float, long)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_setStateWithUpdateTime() {
+        PlaybackStateCompat state = new PlaybackStateCompat.Builder()
+                .setState(
+                        PlaybackStateCompat.STATE_REWINDING,
+                        TEST_POSITION,
+                        TEST_PLAYBACK_SPEED_ON_REWIND,
+                        TEST_UPDATE_TIME)
+                .build();
+        assertEquals(PlaybackStateCompat.STATE_REWINDING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(String, String, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomAction() {
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .build());
+            builder.addCustomAction(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.Builder#addCustomAction(PlaybackStateCompat.CustomAction)}.
+     */
+    @Test
+    @SmallTest
+    public void testBuilder_addCustomActionWithCustomActionObject() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        ArrayList<PlaybackStateCompat.CustomAction> actions = new ArrayList<>();
+        PlaybackStateCompat.Builder builder = new PlaybackStateCompat.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+            builder.addCustomAction(new PlaybackStateCompat.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+        }
+
+        PlaybackStateCompat state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#writeToParcel(Parcel, int)}.
+     */
+    @Test
+    @SmallTest
+    public void testWriteToParcel() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackStateCompat.Builder builder =
+                new PlaybackStateCompat.Builder()
+                        .setState(PlaybackStateCompat.STATE_CONNECTING, TEST_POSITION,
+                                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
+                        .setActions(TEST_ACTIONS)
+                        .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                        .setBufferedPosition(TEST_BUFFERED_POSITION)
+                        .setErrorMessage(TEST_ERROR_MSG)
+                        .setExtras(extras);
+
+        for (int i = 0; i < 5; i++) {
+            builder.addCustomAction(
+                    new PlaybackStateCompat.CustomAction.Builder(
+                            TEST_CUSTOM_ACTION + i,
+                            TEST_CUSTOM_ACTION_NAME + i,
+                            TEST_ICON_RESOURCE_ID + i)
+                            .setExtras(extras)
+                            .build());
+        }
+        PlaybackStateCompat state = builder.build();
+
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        PlaybackStateCompat stateOut = PlaybackStateCompat.CREATOR.createFromParcel(parcel);
+        assertEquals(PlaybackStateCompat.STATE_CONNECTING, stateOut.getState());
+        assertEquals(TEST_POSITION, stateOut.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
+        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
+        assertEquals(TEST_ACTIONS, stateOut.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
+        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
+        assertNotNull(stateOut.getExtras());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+
+        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
+        for (int i = 0; i < state.getCustomActions().size(); i++) {
+            assertCustomActionEquals(
+                    state.getCustomActions().get(i), stateOut.getCustomActions().get(i));
+        }
+        parcel.recycle();
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat#describeContents()}.
+     */
+    @Test
+    @SmallTest
+    public void testDescribeContents() {
+        assertEquals(0, new PlaybackStateCompat.Builder().build().describeContents());
+    }
+
+    /**
+     * Test {@link PlaybackStateCompat.CustomAction}.
+     */
+    @Test
+    @SmallTest
+    public void testCustomAction() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        // Test Builder/Getters
+        PlaybackStateCompat.CustomAction customAction = new PlaybackStateCompat.CustomAction
+                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
+                .setExtras(extras)
+                .build();
+        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
+        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
+        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
+        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
+
+        // Test describeContents
+        assertEquals(0, customAction.describeContents());
+
+        // Test writeToParcel
+        Parcel parcel = Parcel.obtain();
+        customAction.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        assertCustomActionEquals(
+                customAction, PlaybackStateCompat.CustomAction.CREATOR.createFromParcel(parcel));
+        parcel.recycle();
+    }
+
+    private void assertCustomActionEquals(PlaybackStateCompat.CustomAction action1,
+            PlaybackStateCompat.CustomAction action2) {
+        assertEquals(action1.getAction(), action2.getAction());
+        assertEquals(action1.getName(), action2.getName());
+        assertEquals(action1.getIcon(), action2.getIcon());
+
+        // To be the same, two extras should be both null or both not null.
+        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
+        if (action1.getExtras() != null) {
+            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
+        }
+    }
+}
diff --git a/media-compat/tests/src/android/support/v4/media/session/TestActivity.java b/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
deleted file mode 100644
index dd56467..0000000
--- a/media-compat/tests/src/android/support/v4/media/session/TestActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2016 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.support.v4.media.session;
-
-import android.app.Activity;
-
-public class TestActivity extends Activity {
-}
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/AlbumArtCache.java b/samples/Support4Demos/src/com/example/android/supportv4/media/AlbumArtCache.java
index 630b5d5..22a1e7b 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/AlbumArtCache.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/AlbumArtCache.java
@@ -18,8 +18,9 @@
 
 import android.graphics.Bitmap;
 import android.os.AsyncTask;
+import android.support.v4.graphics.BitmapCompat;
+import android.support.v4.util.LruCache;
 import android.util.Log;
-import android.util.LruCache;
 
 import com.example.android.supportv4.media.utils.BitmapHelper;
 
@@ -61,8 +62,8 @@
         mCache = new LruCache<String, Bitmap[]>(maxSize) {
             @Override
             protected int sizeOf(String key, Bitmap[] value) {
-                return value[BIG_BITMAP_INDEX].getByteCount()
-                    + value[ICON_BITMAP_INDEX].getByteCount();
+                return BitmapCompat.getAllocationByteCount(value[BIG_BITMAP_INDEX])
+                        + BitmapCompat.getAllocationByteCount(value[ICON_BITMAP_INDEX]);
             }
         };
     }
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
index 9fac4ab..2e49a1b 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
@@ -15,11 +15,11 @@
  */
 package com.example.android.supportv4.media;
 
-import android.app.Fragment;
 import android.content.ComponentName;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.app.Fragment;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaControllerCompat;
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserSupport.java
index 6460318..55176d8 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserSupport.java
@@ -16,16 +16,18 @@
 
 package com.example.android.supportv4.media;
 
-import com.example.android.supportv4.R;
-import android.app.Activity;
 import android.os.Bundle;
+import android.support.v4.app.FragmentActivity;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaControllerCompat;
 
+import com.example.android.supportv4.R;
+
 /**
  * Main activity for the music player.
  */
-public class MediaBrowserSupport extends Activity implements BrowseFragment.FragmentDataHelper {
+public class MediaBrowserSupport extends FragmentActivity
+        implements BrowseFragment.FragmentDataHelper {
     private MediaControllerCompat mMediaController;
 
     @Override
@@ -33,7 +35,7 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_player);
         if (savedInstanceState == null) {
-            getFragmentManager().beginTransaction()
+            getSupportFragmentManager().beginTransaction()
                     .add(R.id.container, BrowseFragment.newInstance(null))
                     .commit();
         }
@@ -44,12 +46,12 @@
         if (item.isPlayable()) {
             mMediaController.getTransportControls().playFromMediaId(item.getMediaId(), null);
             QueueFragment queueFragment = QueueFragment.newInstance();
-            getFragmentManager().beginTransaction()
+            getSupportFragmentManager().beginTransaction()
                     .replace(R.id.container, queueFragment)
                     .addToBackStack(null)
                     .commit();
         } else if (item.isBrowsable()) {
-            getFragmentManager().beginTransaction()
+            getSupportFragmentManager().beginTransaction()
                     .replace(R.id.container, BrowseFragment.newInstance(item.getMediaId()))
                     .addToBackStack(null)
                     .commit();
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
index 6ec5477..33e6888 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/QueueFragment.java
@@ -16,10 +16,10 @@
 
 package com.example.android.supportv4.media;
 
-import android.app.Fragment;
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.v4.app.Fragment;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.media.MediaBrowserCompat;
 import android.support.v4.media.session.MediaControllerCompat;
diff --git a/samples/SupportLeanbackJank/res/raw/bbb_480p.mp4 b/samples/SupportLeanbackJank/res/raw/bbb_480p.mp4
new file mode 100644
index 0000000..9beaff3
--- /dev/null
+++ b/samples/SupportLeanbackJank/res/raw/bbb_480p.mp4
Binary files differ
diff --git a/samples/SupportLeanbackJank/res/raw/bbb_sunflower_2160p_60fps.mp4 b/samples/SupportLeanbackJank/res/raw/bbb_sunflower_2160p_60fps.mp4
new file mode 100644
index 0000000..60f0bec
--- /dev/null
+++ b/samples/SupportLeanbackJank/res/raw/bbb_sunflower_2160p_60fps.mp4
Binary files differ
diff --git a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentDefaults.java b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentDefaults.java
index 7eba903..722b7d6 100644
--- a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentDefaults.java
+++ b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentDefaults.java
@@ -22,6 +22,7 @@
     public static final int ENTRIES_PER_CATEGORY = 20;
     public static final int CARD_WIDTH = 313;
     public static final int CARD_HEIGHT = 176;
+    public static final int WHICH_VIDEO = IntentKeys.NO_VIDEO;
     public static final boolean DISABLE_SHADOWS = false;
     public static final boolean PLAY_VIDEO = false;
     public static final boolean USE_SINGLE_BITMAP = false;
diff --git a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentKeys.java b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentKeys.java
index c25ed0a..6d04cb0 100644
--- a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentKeys.java
+++ b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/IntentKeys.java
@@ -23,9 +23,15 @@
     public static final String CARD_WIDTH = "CARD_WIDTH";
     public static final String CARD_HEIGHT = "CARD_HEIGHT";
     public static final String DISABLE_SHADOWS = "ENABLE_SHADOWS";
-    public static final String PLAY_VIDEO = "PLAY_VIDEO";
+    public static final String WHICH_VIDEO = "WHICH_VIDEO";
     public static final String USE_SINGLE_BITMAP = "USE_SINGLE_BITMAP";
 
+    // Define values for WHICH_VIDEO.
+    public static final int NO_VIDEO = 0;
+    public static final int VIDEO_480P_60FPS = 1;
+    public static final int VIDEO_1080P_60FPS = 2;
+    public static final int VIDEO_2160P_60FPS = 3;
+
     private IntentKeys() {
     }
 }
diff --git a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/ui/MainFragment.java b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/ui/MainFragment.java
index 507c562..5cbff96 100644
--- a/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/ui/MainFragment.java
+++ b/samples/SupportLeanbackJank/src/com/google/android/leanbackjank/ui/MainFragment.java
@@ -59,7 +59,7 @@
         boolean disableShadows = IntentDefaults.DISABLE_SHADOWS;
         int cardWidth = IntentDefaults.CARD_WIDTH;
         int cardHeight = IntentDefaults.CARD_HEIGHT;
-        boolean playVideo = IntentDefaults.PLAY_VIDEO;
+        int whichVideo = IntentDefaults.WHICH_VIDEO;
         boolean useSingleBitmap = IntentDefaults.USE_SINGLE_BITMAP;
 
         Intent intent = getActivity().getIntent();
@@ -69,7 +69,7 @@
             disableShadows = intent.getBooleanExtra(IntentKeys.DISABLE_SHADOWS, disableShadows);
             cardWidth = intent.getIntExtra(IntentKeys.CARD_WIDTH, cardWidth);
             cardHeight = intent.getIntExtra(IntentKeys.CARD_HEIGHT, cardHeight);
-            playVideo = intent.getBooleanExtra(IntentKeys.PLAY_VIDEO, playVideo);
+            whichVideo = intent.getIntExtra(IntentKeys.WHICH_VIDEO, whichVideo);
             useSingleBitmap = intent.getBooleanExtra(IntentKeys.USE_SINGLE_BITMAP, useSingleBitmap);
         }
 
@@ -78,9 +78,20 @@
         setBackground();
         setupUIElements();
 
-        if (playVideo) {
+        if (whichVideo != IntentKeys.NO_VIDEO) {
+            int resource = 0;
+            /* For info on how to generate videos see:
+             * https://docs.google.com/document/d/1HV8O-Nm4rc2DwVwiZmT4Wa9pf8XttWndg9saGncTRGw
+             */
+            if (whichVideo == IntentKeys.VIDEO_2160P_60FPS) {
+                resource = R.raw.bbb_sunflower_2160p_60fps;
+            } else if (whichVideo == IntentKeys.VIDEO_1080P_60FPS) {
+                resource = R.raw.testvideo_1080p_60fps;
+            } else if (whichVideo == IntentKeys.VIDEO_480P_60FPS) {
+                resource = R.raw.bbb_480p;
+            }
             Uri uri = Uri.parse("android.resource://" + getContext().getPackageName() + "/"
-                    + R.raw.testvideo_1080p_60fps);
+                    + resource);
             Intent videoIntent = new Intent(Intent.ACTION_VIEW, uri, getContext(),
                     VideoActivity.class);
             startActivity(videoIntent);
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az/strings.xml
similarity index 100%
rename from v17/leanback/res/values-az-rAZ/strings.xml
rename to v17/leanback/res/values-az/strings.xml
diff --git a/v17/leanback/res/values-be-rBY/strings.xml b/v17/leanback/res/values-be/strings.xml
similarity index 100%
rename from v17/leanback/res/values-be-rBY/strings.xml
rename to v17/leanback/res/values-be/strings.xml
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-bn-rBD/strings.xml
rename to v17/leanback/res/values-bn/strings.xml
diff --git a/v17/leanback/res/values-bs-rBA/strings.xml b/v17/leanback/res/values-bs/strings.xml
similarity index 100%
rename from v17/leanback/res/values-bs-rBA/strings.xml
rename to v17/leanback/res/values-bs/strings.xml
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et/strings.xml
similarity index 100%
rename from v17/leanback/res/values-et-rEE/strings.xml
rename to v17/leanback/res/values-et/strings.xml
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-eu-rES/strings.xml
rename to v17/leanback/res/values-eu/strings.xml
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl/strings.xml
similarity index 100%
rename from v17/leanback/res/values-gl-rES/strings.xml
rename to v17/leanback/res/values-gl/strings.xml
diff --git a/v17/leanback/res/values-gu-rIN/strings.xml b/v17/leanback/res/values-gu/strings.xml
similarity index 100%
rename from v17/leanback/res/values-gu-rIN/strings.xml
rename to v17/leanback/res/values-gu/strings.xml
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy/strings.xml
similarity index 100%
rename from v17/leanback/res/values-hy-rAM/strings.xml
rename to v17/leanback/res/values-hy/strings.xml
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is/strings.xml
similarity index 100%
rename from v17/leanback/res/values-is-rIS/strings.xml
rename to v17/leanback/res/values-is/strings.xml
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ka-rGE/strings.xml
rename to v17/leanback/res/values-ka/strings.xml
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-kk-rKZ/strings.xml
rename to v17/leanback/res/values-kk/strings.xml
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km/strings.xml
similarity index 100%
rename from v17/leanback/res/values-km-rKH/strings.xml
rename to v17/leanback/res/values-km/strings.xml
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-kn-rIN/strings.xml
rename to v17/leanback/res/values-kn/strings.xml
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ky-rKG/strings.xml
rename to v17/leanback/res/values-ky/strings.xml
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo/strings.xml
similarity index 100%
rename from v17/leanback/res/values-lo-rLA/strings.xml
rename to v17/leanback/res/values-lo/strings.xml
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mk-rMK/strings.xml
rename to v17/leanback/res/values-mk/strings.xml
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ml-rIN/strings.xml
rename to v17/leanback/res/values-ml/strings.xml
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mn-rMN/strings.xml
rename to v17/leanback/res/values-mn/strings.xml
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr/strings.xml
similarity index 100%
rename from v17/leanback/res/values-mr-rIN/strings.xml
rename to v17/leanback/res/values-mr/strings.xml
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ms-rMY/strings.xml
rename to v17/leanback/res/values-ms/strings.xml
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my/strings.xml
similarity index 100%
rename from v17/leanback/res/values-my-rMM/strings.xml
rename to v17/leanback/res/values-my/strings.xml
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ne-rNP/strings.xml
rename to v17/leanback/res/values-ne/strings.xml
diff --git a/v17/leanback/res/values-pa-rIN/strings.xml b/v17/leanback/res/values-pa/strings.xml
similarity index 100%
rename from v17/leanback/res/values-pa-rIN/strings.xml
rename to v17/leanback/res/values-pa/strings.xml
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si/strings.xml
similarity index 100%
rename from v17/leanback/res/values-si-rLK/strings.xml
rename to v17/leanback/res/values-si/strings.xml
diff --git a/v17/leanback/res/values-sq-rAL/strings.xml b/v17/leanback/res/values-sq/strings.xml
similarity index 100%
rename from v17/leanback/res/values-sq-rAL/strings.xml
rename to v17/leanback/res/values-sq/strings.xml
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ta-rIN/strings.xml
rename to v17/leanback/res/values-ta/strings.xml
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te/strings.xml
similarity index 100%
rename from v17/leanback/res/values-te-rIN/strings.xml
rename to v17/leanback/res/values-te/strings.xml
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur/strings.xml
similarity index 100%
rename from v17/leanback/res/values-ur-rPK/strings.xml
rename to v17/leanback/res/values-ur/strings.xml
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz/strings.xml
similarity index 100%
rename from v17/leanback/res/values-uz-rUZ/strings.xml
rename to v17/leanback/res/values-uz/strings.xml
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
index 8e3b4f4..f9af12f 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ListRowDataAdapter.java
@@ -124,7 +124,7 @@
             int totalItems = lastVisibleRowIndex - mLastVisibleRowIndex;
             if (totalItems > 0) {
                 onEventFired(ON_ITEM_RANGE_REMOVED,
-                        Math.min(lastVisibleRowIndex + 1, positionStart),
+                        Math.min(mLastVisibleRowIndex + 1, positionStart),
                         totalItems);
             }
         }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
index b515a18..0b40920 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/ListRowDataAdapterTest.java
@@ -217,6 +217,28 @@
     }
 
     @Test
+    public void changeRemove_revealInvisibleItems() {
+        ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
+        for (int i = 0; i < 4; i++) {
+            HeaderItem headerItem = new HeaderItem(i, "header "+i);
+            adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        }
+        adapter.add(new SectionRow("section"));
+        for (int i = 4; i < 8; i++) {
+            HeaderItem headerItem = new HeaderItem(i, "header "+i);
+            adapter.add(new ListRow(headerItem, createListRowAdapter()));
+        }
+
+        ListRowDataAdapter listRowDataAdapter = new ListRowDataAdapter(adapter);
+        assertEquals(9, listRowDataAdapter.size());
+
+        listRowDataAdapter.registerObserver(dataObserver);
+        adapter.removeItems(5, 4);
+        verify(dataObserver, times(1)).onItemRangeRemoved(4, 5);
+        assertEquals(4, listRowDataAdapter.size());
+    }
+
+    @Test
     public void adapterSize_rowsRemoved() {
         int itemCount = 4;
         ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenterSelector);
diff --git a/v7/appcompat/res/values-az-rAZ/strings.xml b/v7/appcompat/res/values-az/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-az-rAZ/strings.xml
rename to v7/appcompat/res/values-az/strings.xml
diff --git a/v7/appcompat/res/values-be-rBY/strings.xml b/v7/appcompat/res/values-be/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-be-rBY/strings.xml
rename to v7/appcompat/res/values-be/strings.xml
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-bn-rBD/strings.xml
rename to v7/appcompat/res/values-bn/strings.xml
diff --git a/v7/appcompat/res/values-bs-rBA/strings.xml b/v7/appcompat/res/values-bs/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-bs-rBA/strings.xml
rename to v7/appcompat/res/values-bs/strings.xml
diff --git a/v7/appcompat/res/values-et-rEE/strings.xml b/v7/appcompat/res/values-et/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-et-rEE/strings.xml
rename to v7/appcompat/res/values-et/strings.xml
diff --git a/v7/appcompat/res/values-eu-rES/strings.xml b/v7/appcompat/res/values-eu/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-eu-rES/strings.xml
rename to v7/appcompat/res/values-eu/strings.xml
diff --git a/v7/appcompat/res/values-gl-rES/strings.xml b/v7/appcompat/res/values-gl/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-gl-rES/strings.xml
rename to v7/appcompat/res/values-gl/strings.xml
diff --git a/v7/appcompat/res/values-gu-rIN/strings.xml b/v7/appcompat/res/values-gu/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-gu-rIN/strings.xml
rename to v7/appcompat/res/values-gu/strings.xml
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-hy-rAM/strings.xml
rename to v7/appcompat/res/values-hy/strings.xml
diff --git a/v7/appcompat/res/values-is-rIS/strings.xml b/v7/appcompat/res/values-is/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-is-rIS/strings.xml
rename to v7/appcompat/res/values-is/strings.xml
diff --git a/v7/appcompat/res/values-ka-rGE/strings.xml b/v7/appcompat/res/values-ka/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ka-rGE/strings.xml
rename to v7/appcompat/res/values-ka/strings.xml
diff --git a/v7/appcompat/res/values-kk-rKZ/strings.xml b/v7/appcompat/res/values-kk/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-kk-rKZ/strings.xml
rename to v7/appcompat/res/values-kk/strings.xml
diff --git a/v7/appcompat/res/values-km-rKH/strings.xml b/v7/appcompat/res/values-km/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-km-rKH/strings.xml
rename to v7/appcompat/res/values-km/strings.xml
diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-kn-rIN/strings.xml
rename to v7/appcompat/res/values-kn/strings.xml
diff --git a/v7/appcompat/res/values-ky-rKG/strings.xml b/v7/appcompat/res/values-ky/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ky-rKG/strings.xml
rename to v7/appcompat/res/values-ky/strings.xml
diff --git a/v7/appcompat/res/values-lo-rLA/strings.xml b/v7/appcompat/res/values-lo/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-lo-rLA/strings.xml
rename to v7/appcompat/res/values-lo/strings.xml
diff --git a/v7/appcompat/res/values-mk-rMK/strings.xml b/v7/appcompat/res/values-mk/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-mk-rMK/strings.xml
rename to v7/appcompat/res/values-mk/strings.xml
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ml-rIN/strings.xml
rename to v7/appcompat/res/values-ml/strings.xml
diff --git a/v7/appcompat/res/values-mn-rMN/strings.xml b/v7/appcompat/res/values-mn/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-mn-rMN/strings.xml
rename to v7/appcompat/res/values-mn/strings.xml
diff --git a/v7/appcompat/res/values-mr-rIN/strings.xml b/v7/appcompat/res/values-mr/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-mr-rIN/strings.xml
rename to v7/appcompat/res/values-mr/strings.xml
diff --git a/v7/appcompat/res/values-ms-rMY/strings.xml b/v7/appcompat/res/values-ms/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ms-rMY/strings.xml
rename to v7/appcompat/res/values-ms/strings.xml
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-my-rMM/strings.xml
rename to v7/appcompat/res/values-my/strings.xml
diff --git a/v7/appcompat/res/values-ne-rNP/strings.xml b/v7/appcompat/res/values-ne/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ne-rNP/strings.xml
rename to v7/appcompat/res/values-ne/strings.xml
diff --git a/v7/appcompat/res/values-pa-rIN/strings.xml b/v7/appcompat/res/values-pa/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-pa-rIN/strings.xml
rename to v7/appcompat/res/values-pa/strings.xml
diff --git a/v7/appcompat/res/values-si-rLK/strings.xml b/v7/appcompat/res/values-si/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-si-rLK/strings.xml
rename to v7/appcompat/res/values-si/strings.xml
diff --git a/v7/appcompat/res/values-sq-rAL/strings.xml b/v7/appcompat/res/values-sq/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-sq-rAL/strings.xml
rename to v7/appcompat/res/values-sq/strings.xml
diff --git a/v7/appcompat/res/values-ta-rIN/strings.xml b/v7/appcompat/res/values-ta/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ta-rIN/strings.xml
rename to v7/appcompat/res/values-ta/strings.xml
diff --git a/v7/appcompat/res/values-te-rIN/strings.xml b/v7/appcompat/res/values-te/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-te-rIN/strings.xml
rename to v7/appcompat/res/values-te/strings.xml
diff --git a/v7/appcompat/res/values-ur-rPK/strings.xml b/v7/appcompat/res/values-ur/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-ur-rPK/strings.xml
rename to v7/appcompat/res/values-ur/strings.xml
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz/strings.xml
similarity index 100%
rename from v7/appcompat/res/values-uz-rUZ/strings.xml
rename to v7/appcompat/res/values-uz/strings.xml
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 89a6bdb..65abe2e 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -16,8 +16,6 @@
 
 package android.support.v7.app;
 
-import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -28,7 +26,6 @@
 import android.support.annotation.LayoutRes;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.annotation.RestrictTo;
 import android.support.annotation.StyleRes;
 import android.support.v4.app.ActivityCompat;
 import android.support.v4.app.FragmentActivity;
@@ -242,10 +239,7 @@
         getDelegate().invalidateOptionsMenu();
     }
 
-    /**
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP)
+    @Override
     public void invalidateOptionsMenu() {
         getDelegate().invalidateOptionsMenu();
     }
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index eb2a053..b9adadc 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -509,8 +509,6 @@
         return mSearchSrcTextView.getInputType();
     }
 
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP)
     @Override
     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
         // Don't accept focus if in the middle of clearing focus
@@ -529,8 +527,6 @@
         }
     }
 
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP)
     @Override
     public void clearFocus() {
         mClearingFocus = true;
diff --git a/v7/mediarouter/res/values-az-rAZ/strings.xml b/v7/mediarouter/res/values-az/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-az-rAZ/strings.xml
rename to v7/mediarouter/res/values-az/strings.xml
diff --git a/v7/mediarouter/res/values-be-rBY/strings.xml b/v7/mediarouter/res/values-be/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-be-rBY/strings.xml
rename to v7/mediarouter/res/values-be/strings.xml
diff --git a/v7/mediarouter/res/values-bn-rBD/strings.xml b/v7/mediarouter/res/values-bn/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-bn-rBD/strings.xml
rename to v7/mediarouter/res/values-bn/strings.xml
diff --git a/v7/mediarouter/res/values-bs-rBA/strings.xml b/v7/mediarouter/res/values-bs/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-bs-rBA/strings.xml
rename to v7/mediarouter/res/values-bs/strings.xml
diff --git a/v7/mediarouter/res/values-et-rEE/strings.xml b/v7/mediarouter/res/values-et/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-et-rEE/strings.xml
rename to v7/mediarouter/res/values-et/strings.xml
diff --git a/v7/mediarouter/res/values-eu-rES/strings.xml b/v7/mediarouter/res/values-eu/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-eu-rES/strings.xml
rename to v7/mediarouter/res/values-eu/strings.xml
diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-gl-rES/strings.xml
rename to v7/mediarouter/res/values-gl/strings.xml
diff --git a/v7/mediarouter/res/values-gu-rIN/strings.xml b/v7/mediarouter/res/values-gu/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-gu-rIN/strings.xml
rename to v7/mediarouter/res/values-gu/strings.xml
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
index e74967e..5b01c7c 100644
--- a/v7/mediarouter/res/values-hi/strings.xml
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -19,12 +19,12 @@
     <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string>
     <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिवाइस"</string>
     <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट करें बटन"</string>
-    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिस्कनेक्ट है"</string>
+    <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिसकनेक्ट है"</string>
     <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट करें बटन. कनेक्ट हो रहा है"</string>
     <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट करें बटन. कनेक्ट है"</string>
     <string name="mr_chooser_title" msgid="414301941546135990">"इस पर कास्‍ट करें"</string>
     <string name="mr_chooser_searching" msgid="6349900579507521956">"डिवाइस ढूंढ रहा है"</string>
-    <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिस्कनेक्ट करें"</string>
+    <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिसकनेक्ट करें"</string>
     <string name="mr_controller_stop" msgid="4570331844078181931">"कास्ट करना बंद करें"</string>
     <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करें"</string>
     <string name="mr_controller_play" msgid="683634565969987458">"चलाएं"</string>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-hy-rAM/strings.xml
rename to v7/mediarouter/res/values-hy/strings.xml
diff --git a/v7/mediarouter/res/values-is-rIS/strings.xml b/v7/mediarouter/res/values-is/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-is-rIS/strings.xml
rename to v7/mediarouter/res/values-is/strings.xml
diff --git a/v7/mediarouter/res/values-ka-rGE/strings.xml b/v7/mediarouter/res/values-ka/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ka-rGE/strings.xml
rename to v7/mediarouter/res/values-ka/strings.xml
diff --git a/v7/mediarouter/res/values-kk-rKZ/strings.xml b/v7/mediarouter/res/values-kk/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-kk-rKZ/strings.xml
rename to v7/mediarouter/res/values-kk/strings.xml
diff --git a/v7/mediarouter/res/values-km-rKH/strings.xml b/v7/mediarouter/res/values-km/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-km-rKH/strings.xml
rename to v7/mediarouter/res/values-km/strings.xml
diff --git a/v7/mediarouter/res/values-kn-rIN/strings.xml b/v7/mediarouter/res/values-kn/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-kn-rIN/strings.xml
rename to v7/mediarouter/res/values-kn/strings.xml
diff --git a/v7/mediarouter/res/values-ky-rKG/strings.xml b/v7/mediarouter/res/values-ky/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ky-rKG/strings.xml
rename to v7/mediarouter/res/values-ky/strings.xml
diff --git a/v7/mediarouter/res/values-lo-rLA/strings.xml b/v7/mediarouter/res/values-lo/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-lo-rLA/strings.xml
rename to v7/mediarouter/res/values-lo/strings.xml
diff --git a/v7/mediarouter/res/values-mk-rMK/strings.xml b/v7/mediarouter/res/values-mk/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-mk-rMK/strings.xml
rename to v7/mediarouter/res/values-mk/strings.xml
diff --git a/v7/mediarouter/res/values-ml-rIN/strings.xml b/v7/mediarouter/res/values-ml/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ml-rIN/strings.xml
rename to v7/mediarouter/res/values-ml/strings.xml
diff --git a/v7/mediarouter/res/values-mn-rMN/strings.xml b/v7/mediarouter/res/values-mn/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-mn-rMN/strings.xml
rename to v7/mediarouter/res/values-mn/strings.xml
diff --git a/v7/mediarouter/res/values-mr-rIN/strings.xml b/v7/mediarouter/res/values-mr/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-mr-rIN/strings.xml
rename to v7/mediarouter/res/values-mr/strings.xml
diff --git a/v7/mediarouter/res/values-ms-rMY/strings.xml b/v7/mediarouter/res/values-ms/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ms-rMY/strings.xml
rename to v7/mediarouter/res/values-ms/strings.xml
diff --git a/v7/mediarouter/res/values-my-rMM/strings.xml b/v7/mediarouter/res/values-my/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-my-rMM/strings.xml
rename to v7/mediarouter/res/values-my/strings.xml
diff --git a/v7/mediarouter/res/values-ne-rNP/strings.xml b/v7/mediarouter/res/values-ne/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ne-rNP/strings.xml
rename to v7/mediarouter/res/values-ne/strings.xml
diff --git a/v7/mediarouter/res/values-pa-rIN/strings.xml b/v7/mediarouter/res/values-pa/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-pa-rIN/strings.xml
rename to v7/mediarouter/res/values-pa/strings.xml
diff --git a/v7/mediarouter/res/values-si-rLK/strings.xml b/v7/mediarouter/res/values-si/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-si-rLK/strings.xml
rename to v7/mediarouter/res/values-si/strings.xml
diff --git a/v7/mediarouter/res/values-sq-rAL/strings.xml b/v7/mediarouter/res/values-sq/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-sq-rAL/strings.xml
rename to v7/mediarouter/res/values-sq/strings.xml
diff --git a/v7/mediarouter/res/values-ta-rIN/strings.xml b/v7/mediarouter/res/values-ta/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ta-rIN/strings.xml
rename to v7/mediarouter/res/values-ta/strings.xml
diff --git a/v7/mediarouter/res/values-te-rIN/strings.xml b/v7/mediarouter/res/values-te/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-te-rIN/strings.xml
rename to v7/mediarouter/res/values-te/strings.xml
diff --git a/v7/mediarouter/res/values-ur-rPK/strings.xml b/v7/mediarouter/res/values-ur/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-ur-rPK/strings.xml
rename to v7/mediarouter/res/values-ur/strings.xml
diff --git a/v7/mediarouter/res/values-uz-rUZ/strings.xml b/v7/mediarouter/res/values-uz/strings.xml
similarity index 100%
rename from v7/mediarouter/res/values-uz-rUZ/strings.xml
rename to v7/mediarouter/res/values-uz/strings.xml
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index f388c3f..3a2291b 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -15,8 +15,9 @@
  */
 package android.support.v7.widget;
 
+import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
 import android.support.annotation.NonNull;
-import android.support.v4.animation.AnimatorCompatHelper;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.ViewPropertyAnimatorCompat;
 import android.support.v4.view.ViewPropertyAnimatorListener;
@@ -36,6 +37,8 @@
 public class DefaultItemAnimator extends SimpleItemAnimator {
     private static final boolean DEBUG = false;
 
+    private static TimeInterpolator sDefaultInterpolator;
+
     private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
     private ArrayList<ViewHolder> mPendingAdditions = new ArrayList<>();
     private ArrayList<MoveInfo> mPendingMoves = new ArrayList<>();
@@ -512,7 +515,10 @@
     }
 
     private void resetAnimation(ViewHolder holder) {
-        AnimatorCompatHelper.clearInterpolator(holder.itemView);
+        if (sDefaultInterpolator == null) {
+            sDefaultInterpolator = new ValueAnimator().getInterpolator();
+        }
+        holder.itemView.animate().setInterpolator(sDefaultInterpolator);
         endAnimation(holder);
     }
 
diff --git a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
index cce7161..0d39fed 100644
--- a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -16,15 +16,13 @@
 
 package android.support.v7.widget.helper;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Build;
 import android.support.annotation.Nullable;
-import android.support.v4.animation.AnimatorCompatHelper;
-import android.support.v4.animation.AnimatorListenerCompat;
-import android.support.v4.animation.AnimatorUpdateListenerCompat;
-import android.support.v4.animation.ValueAnimatorCompat;
 import android.support.v4.view.GestureDetectorCompat;
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.VelocityTrackerCompat;
@@ -609,7 +607,7 @@
                         prevActionState, currentTranslateX, currentTranslateY,
                         targetTranslateX, targetTranslateY) {
                     @Override
-                    public void onAnimationEnd(ValueAnimatorCompat animation) {
+                    public void onAnimationEnd(Animator animation) {
                         super.onAnimationEnd(animation);
                         if (this.mOverridden) {
                             return;
@@ -2297,7 +2295,7 @@
         }
     }
 
-    private class RecoverAnimation implements AnimatorListenerCompat {
+    private class RecoverAnimation implements Animator.AnimatorListener {
 
         final float mStartDx;
 
@@ -2311,7 +2309,7 @@
 
         final int mActionState;
 
-        private final ValueAnimatorCompat mValueAnimator;
+        private final ValueAnimator mValueAnimator;
 
         final int mAnimationType;
 
@@ -2338,11 +2336,11 @@
             mStartDy = startDy;
             mTargetX = targetX;
             mTargetY = targetY;
-            mValueAnimator = AnimatorCompatHelper.emptyValueAnimator();
+            mValueAnimator = ValueAnimator.ofFloat(0f, 1f);
             mValueAnimator.addUpdateListener(
-                    new AnimatorUpdateListenerCompat() {
+                    new ValueAnimator.AnimatorUpdateListener() {
                         @Override
-                        public void onAnimationUpdate(ValueAnimatorCompat animation) {
+                        public void onAnimationUpdate(ValueAnimator animation) {
                             setFraction(animation.getAnimatedFraction());
                         }
                     });
@@ -2386,12 +2384,12 @@
         }
 
         @Override
-        public void onAnimationStart(ValueAnimatorCompat animation) {
+        public void onAnimationStart(Animator animation) {
 
         }
 
         @Override
-        public void onAnimationEnd(ValueAnimatorCompat animation) {
+        public void onAnimationEnd(Animator animation) {
             if (!mEnded) {
                 mViewHolder.setIsRecyclable(true);
             }
@@ -2399,12 +2397,12 @@
         }
 
         @Override
-        public void onAnimationCancel(ValueAnimatorCompat animation) {
+        public void onAnimationCancel(Animator animation) {
             setFraction(1f); //make sure we recover the view's state.
         }
 
         @Override
-        public void onAnimationRepeat(ValueAnimatorCompat animation) {
+        public void onAnimationRepeat(Animator animation) {
 
         }
     }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
index 64111c5..d92b169 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -279,7 +279,10 @@
                                 item.mText + " (" + item.mId + ")");
                     }
                 });
-        waitFirstLayout();
+        mLayoutManager.expectLayouts(1);
+        setRecyclerView(mRecyclerView);
+        mLayoutManager.waitForLayout(10);
+        getInstrumentation().waitForIdleSync();
         ViewGroup lastChild = (ViewGroup) mRecyclerView.getChildAt(
                 mRecyclerView.getChildCount() - 1);
         RecyclerView.ViewHolder lastViewHolder = mRecyclerView.getChildViewHolder(lastChild);