Import Android SDK Platform P [4697573]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4697573 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4697573.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: If80578c3c657366cc9cf75f8db13d46e2dd4e077
diff --git a/androidx/transition/AnimatorUtils.java b/androidx/transition/AnimatorUtils.java
new file mode 100644
index 0000000..6772a60
--- /dev/null
+++ b/androidx/transition/AnimatorUtils.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+
+class AnimatorUtils {
+
+    static void addPauseListener(@NonNull Animator animator,
+            @NonNull AnimatorListenerAdapter listener) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.addPauseListener(listener);
+        }
+    }
+
+    static void pause(@NonNull Animator animator) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.pause();
+        } else {
+            final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
+            if (listeners != null) {
+                for (int i = 0, size = listeners.size(); i < size; i++) {
+                    final Animator.AnimatorListener listener = listeners.get(i);
+                    if (listener instanceof AnimatorPauseListenerCompat) {
+                        ((AnimatorPauseListenerCompat) listener).onAnimationPause(animator);
+                    }
+                }
+            }
+        }
+    }
+
+    static void resume(@NonNull Animator animator) {
+        if (Build.VERSION.SDK_INT >= 19) {
+            animator.resume();
+        } else {
+            final ArrayList<Animator.AnimatorListener> listeners = animator.getListeners();
+            if (listeners != null) {
+                for (int i = 0, size = listeners.size(); i < size; i++) {
+                    final Animator.AnimatorListener listener = listeners.get(i);
+                    if (listener instanceof AnimatorPauseListenerCompat) {
+                        ((AnimatorPauseListenerCompat) listener).onAnimationResume(animator);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Listeners can implement this interface in addition to the platform AnimatorPauseListener to
+     * make them compatible with API level 18 and below. Animators will not be paused or resumed,
+     * but the callbacks here are invoked.
+     */
+    interface AnimatorPauseListenerCompat {
+
+        void onAnimationPause(Animator animation);
+
+        void onAnimationResume(Animator animation);
+
+    }
+
+    private AnimatorUtils() {
+    }
+}
diff --git a/androidx/transition/ArcMotion.java b/androidx/transition/ArcMotion.java
new file mode 100644
index 0000000..7953389
--- /dev/null
+++ b/androidx/transition/ArcMotion.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Path;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A PathMotion that generates a curved path along an arc on an imaginary circle containing
+ * the two points. If the horizontal distance between the points is less than the vertical
+ * distance, then the circle's center point will be horizontally aligned with the end point. If the
+ * vertical distance is less than the horizontal distance then the circle's center point
+ * will be vertically aligned with the end point.
+ * <p>
+ * When the two points are near horizontal or vertical, the curve of the motion will be
+ * small as the center of the circle will be far from both points. To force curvature of
+ * the path, {@link #setMinimumHorizontalAngle(float)} and
+ * {@link #setMinimumVerticalAngle(float)} may be used to set the minimum angle of the
+ * arc between two points.
+ * </p>
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>{@code
+ * <changeBounds>
+ *   <arcMotion android:minimumHorizontalAngle="15"
+ *              android:minimumVerticalAngle="0"
+ *              android:maximumAngle="90"/>
+ * </changeBounds>}
+ * </pre>
+ */
+public class ArcMotion extends PathMotion {
+
+    private static final float DEFAULT_MIN_ANGLE_DEGREES = 0;
+    private static final float DEFAULT_MAX_ANGLE_DEGREES = 70;
+    private static final float DEFAULT_MAX_TANGENT = (float)
+            Math.tan(Math.toRadians(DEFAULT_MAX_ANGLE_DEGREES / 2));
+
+    private float mMinimumHorizontalAngle = 0;
+    private float mMinimumVerticalAngle = 0;
+    private float mMaximumAngle = DEFAULT_MAX_ANGLE_DEGREES;
+    private float mMinimumHorizontalTangent = 0;
+    private float mMinimumVerticalTangent = 0;
+    private float mMaximumTangent = DEFAULT_MAX_TANGENT;
+
+    public ArcMotion() {
+    }
+
+    public ArcMotion(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.ARC_MOTION);
+        XmlPullParser parser = (XmlPullParser) attrs;
+        float minimumVerticalAngle = TypedArrayUtils.getNamedFloat(a, parser,
+                "minimumVerticalAngle", Styleable.ArcMotion.MINIMUM_VERTICAL_ANGLE,
+                DEFAULT_MIN_ANGLE_DEGREES);
+        setMinimumVerticalAngle(minimumVerticalAngle);
+        float minimumHorizontalAngle = TypedArrayUtils.getNamedFloat(a, parser,
+                "minimumHorizontalAngle", Styleable.ArcMotion.MINIMUM_HORIZONTAL_ANGLE,
+                DEFAULT_MIN_ANGLE_DEGREES);
+        setMinimumHorizontalAngle(minimumHorizontalAngle);
+        float maximumAngle = TypedArrayUtils.getNamedFloat(a, parser, "maximumAngle",
+                Styleable.ArcMotion.MAXIMUM_ANGLE, DEFAULT_MAX_ANGLE_DEGREES);
+        setMaximumAngle(maximumAngle);
+        a.recycle();
+    }
+
+    /**
+     * Sets the minimum arc along the circle between two points aligned near horizontally.
+     * When start and end points are close to horizontal, the calculated center point of the
+     * circle will be far from both points, giving a near straight path between the points.
+     * By setting a minimum angle, this forces the center point to be closer and give an
+     * exaggerated curve to the path.
+     * <p>The default value is 0.</p>
+     *
+     * @param angleInDegrees The minimum angle of the arc on a circle describing the Path
+     *                       between two nearly horizontally-separated points.
+     */
+    public void setMinimumHorizontalAngle(float angleInDegrees) {
+        mMinimumHorizontalAngle = angleInDegrees;
+        mMinimumHorizontalTangent = toTangent(angleInDegrees);
+    }
+
+    /**
+     * Returns the minimum arc along the circle between two points aligned near horizontally.
+     * When start and end points are close to horizontal, the calculated center point of the
+     * circle will be far from both points, giving a near straight path between the points.
+     * By setting a minimum angle, this forces the center point to be closer and give an
+     * exaggerated curve to the path.
+     * <p>The default value is 0.</p>
+     *
+     * @return The minimum arc along the circle between two points aligned near horizontally.
+     */
+    public float getMinimumHorizontalAngle() {
+        return mMinimumHorizontalAngle;
+    }
+
+    /**
+     * Sets the minimum arc along the circle between two points aligned near vertically.
+     * When start and end points are close to vertical, the calculated center point of the
+     * circle will be far from both points, giving a near straight path between the points.
+     * By setting a minimum angle, this forces the center point to be closer and give an
+     * exaggerated curve to the path.
+     * <p>The default value is 0.</p>
+     *
+     * @param angleInDegrees The minimum angle of the arc on a circle describing the Path
+     *                       between two nearly vertically-separated points.
+     */
+    public void setMinimumVerticalAngle(float angleInDegrees) {
+        mMinimumVerticalAngle = angleInDegrees;
+        mMinimumVerticalTangent = toTangent(angleInDegrees);
+    }
+
+    /**
+     * Returns the minimum arc along the circle between two points aligned near vertically.
+     * When start and end points are close to vertical, the calculated center point of the
+     * circle will be far from both points, giving a near straight path between the points.
+     * By setting a minimum angle, this forces the center point to be closer and give an
+     * exaggerated curve to the path.
+     * <p>The default value is 0.</p>
+     *
+     * @return The minimum angle of the arc on a circle describing the Path
+     * between two nearly vertically-separated points.
+     */
+    public float getMinimumVerticalAngle() {
+        return mMinimumVerticalAngle;
+    }
+
+    /**
+     * Sets the maximum arc along the circle between two points. When start and end points
+     * have close to equal x and y differences, the curve between them is large. This forces
+     * the curved path to have an arc of at most the given angle.
+     * <p>The default value is 70 degrees.</p>
+     *
+     * @param angleInDegrees The maximum angle of the arc on a circle describing the Path
+     *                       between the start and end points.
+     */
+    public void setMaximumAngle(float angleInDegrees) {
+        mMaximumAngle = angleInDegrees;
+        mMaximumTangent = toTangent(angleInDegrees);
+    }
+
+    /**
+     * Returns the maximum arc along the circle between two points. When start and end points
+     * have close to equal x and y differences, the curve between them is large. This forces
+     * the curved path to have an arc of at most the given angle.
+     * <p>The default value is 70 degrees.</p>
+     *
+     * @return The maximum angle of the arc on a circle describing the Path
+     * between the start and end points.
+     */
+    public float getMaximumAngle() {
+        return mMaximumAngle;
+    }
+
+    private static float toTangent(float arcInDegrees) {
+        if (arcInDegrees < 0 || arcInDegrees > 90) {
+            throw new IllegalArgumentException("Arc must be between 0 and 90 degrees");
+        }
+        return (float) Math.tan(Math.toRadians(arcInDegrees / 2));
+    }
+
+    @Override
+    public Path getPath(float startX, float startY, float endX, float endY) {
+        // Here's a little ascii art to show how this is calculated:
+        // c---------- b
+        //  \        / |
+        //    \     d  |
+        //      \  /   e
+        //        a----f
+        // This diagram assumes that the horizontal distance is less than the vertical
+        // distance between The start point (a) and end point (b).
+        // d is the midpoint between a and b. c is the center point of the circle with
+        // This path is formed by assuming that start and end points are in
+        // an arc on a circle. The end point is centered in the circle vertically
+        // and start is a point on the circle.
+
+        // Triangles bfa and bde form similar right triangles. The control points
+        // for the cubic Bezier arc path are the midpoints between a and e and e and b.
+
+        Path path = new Path();
+        path.moveTo(startX, startY);
+
+        float ex;
+        float ey;
+        float deltaX = endX - startX;
+        float deltaY = endY - startY;
+
+        // hypotenuse squared.
+        float h2 = deltaX * deltaX + deltaY * deltaY;
+
+        // Midpoint between start and end
+        float dx = (startX + endX) / 2;
+        float dy = (startY + endY) / 2;
+
+        // Distance squared between end point and mid point is (1/2 hypotenuse)^2
+        float midDist2 = h2 * 0.25f;
+
+        float minimumArcDist2;
+
+        boolean isMovingUpwards = startY > endY;
+
+        if ((Math.abs(deltaX) < Math.abs(deltaY))) {
+            // Similar triangles bfa and bde mean that (ab/fb = eb/bd)
+            // Therefore, eb = ab * bd / fb
+            // ab = hypotenuse
+            // bd = hypotenuse/2
+            // fb = deltaY
+            float eDistY = Math.abs(h2 / (2 * deltaY));
+            if (isMovingUpwards) {
+                ey = endY + eDistY;
+                ex = endX;
+            } else {
+                ey = startY + eDistY;
+                ex = startX;
+            }
+
+            minimumArcDist2 = midDist2 * mMinimumVerticalTangent
+                    * mMinimumVerticalTangent;
+        } else {
+            // Same as above, but flip X & Y and account for negative eDist
+            float eDistX = h2 / (2 * deltaX);
+            if (isMovingUpwards) {
+                ex = startX + eDistX;
+                ey = startY;
+            } else {
+                ex = endX - eDistX;
+                ey = endY;
+            }
+
+            minimumArcDist2 = midDist2 * mMinimumHorizontalTangent
+                    * mMinimumHorizontalTangent;
+        }
+        float arcDistX = dx - ex;
+        float arcDistY = dy - ey;
+        float arcDist2 = arcDistX * arcDistX + arcDistY * arcDistY;
+
+        float maximumArcDist2 = midDist2 * mMaximumTangent * mMaximumTangent;
+
+        float newArcDistance2 = 0;
+        if (arcDist2 < minimumArcDist2) {
+            newArcDistance2 = minimumArcDist2;
+        } else if (arcDist2 > maximumArcDist2) {
+            newArcDistance2 = maximumArcDist2;
+        }
+        if (newArcDistance2 != 0) {
+            float ratio2 = newArcDistance2 / arcDist2;
+            float ratio = (float) Math.sqrt(ratio2);
+            ex = dx + (ratio * (ex - dx));
+            ey = dy + (ratio * (ey - dy));
+        }
+        float control1X = (startX + ex) / 2;
+        float control1Y = (startY + ey) / 2;
+        float control2X = (ex + endX) / 2;
+        float control2Y = (ey + endY) / 2;
+        path.cubicTo(control1X, control1Y, control2X, control2Y, endX, endY);
+        return path;
+    }
+
+}
diff --git a/androidx/transition/ArcMotionTest.java b/androidx/transition/ArcMotionTest.java
new file mode 100644
index 0000000..14a704a
--- /dev/null
+++ b/androidx/transition/ArcMotionTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Path;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ArcMotionTest extends PathMotionTest {
+
+    @Test
+    public void test90Quadrants() {
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMaximumAngle(90);
+
+        Path expected = arcWithPoint(0, 100, 100, 0, 100, 100);
+        Path path = arcMotion.getPath(0, 100, 100, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(100, 0, 0, -100, 0, 0);
+        path = arcMotion.getPath(100, 0, 0, -100);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(0, -100, -100, 0, 0, 0);
+        path = arcMotion.getPath(0, -100, -100, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(-100, 0, 0, 100, -100, 100);
+        path = arcMotion.getPath(-100, 0, 0, 100);
+        assertPathMatches(expected, path);
+    }
+
+    @Test
+    public void test345Triangles() {
+        // 3-4-5 triangles are easy to calculate the control points
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMaximumAngle(90);
+        Path expected;
+        Path path;
+
+        expected = arcWithPoint(0, 120, 160, 0, 125, 120);
+        path = arcMotion.getPath(0, 120, 160, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(0, 160, 120, 0, 120, 125);
+        path = arcMotion.getPath(0, 160, 120, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(-120, 0, 0, 160, -120, 125);
+        path = arcMotion.getPath(-120, 0, 0, 160);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(-160, 0, 0, 120, -125, 120);
+        path = arcMotion.getPath(-160, 0, 0, 120);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(0, -120, -160, 0, -35, 0);
+        path = arcMotion.getPath(0, -120, -160, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(0, -160, -120, 0, 0, -35);
+        path = arcMotion.getPath(0, -160, -120, 0);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(120, 0, 0, -160, 0, -35);
+        path = arcMotion.getPath(120, 0, 0, -160);
+        assertPathMatches(expected, path);
+
+        expected = arcWithPoint(160, 0, 0, -120, 35, 0);
+        path = arcMotion.getPath(160, 0, 0, -120);
+        assertPathMatches(expected, path);
+    }
+
+    private static Path arcWithPoint(float startX, float startY, float endX, float endY,
+            float eX, float eY) {
+        float c1x = (eX + startX) / 2;
+        float c1y = (eY + startY) / 2;
+        float c2x = (eX + endX) / 2;
+        float c2y = (eY + endY) / 2;
+        Path path = new Path();
+        path.moveTo(startX, startY);
+        path.cubicTo(c1x, c1y, c2x, c2y, endX, endY);
+        return path;
+    }
+
+    @Test
+    public void testMaximumAngle() {
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMaximumAngle(45f);
+        assertEquals(45f, arcMotion.getMaximumAngle(), 0.0f);
+
+        float ratio = (float) Math.tan(Math.PI / 8);
+        float ex = 50 + (50 * ratio);
+        float ey = ex;
+
+        Path expected = arcWithPoint(0, 100, 100, 0, ex, ey);
+        Path path = arcMotion.getPath(0, 100, 100, 0);
+        assertPathMatches(expected, path);
+    }
+
+    @Test
+    public void testMinimumHorizontalAngle() {
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMinimumHorizontalAngle(45);
+        assertEquals(45, arcMotion.getMinimumHorizontalAngle(), 0.0f);
+
+        float ex = 37.5f;
+        float ey = (float) (Math.tan(Math.PI / 4) * 50);
+        Path expected = arcWithPoint(0, 0, 100, 50, ex, ey);
+        Path path = arcMotion.getPath(0, 0, 100, 50);
+        assertPathMatches(expected, path);
+
+        // Pretty much the same, but follows a different path.
+        expected = arcWithPoint(0, 0, 100.001f, 50, ex, ey);
+        path = arcMotion.getPath(0, 0, 100.001f, 50);
+        assertPathMatches(expected, path);
+
+        // Moving in the opposite direction.
+        expected = arcWithPoint(100, 50, 0, 0, ex, ey);
+        path = arcMotion.getPath(100, 50, 0, 0);
+        assertPathMatches(expected, path);
+
+        // With x < y.
+        ex = 0;
+        ey = (float) (Math.tan(Math.PI / 4) * 62.5f);
+        expected = arcWithPoint(0, 0, 50, 100, ex, ey);
+        path = arcMotion.getPath(0, 0, 50, 100);
+        assertPathMatches(expected, path);
+
+        // Pretty much the same, but follows a different path.
+        expected = arcWithPoint(0, 0, 50, 100.001f, ex, ey);
+        path = arcMotion.getPath(0, 0, 50, 100.001f);
+        assertPathMatches(expected, path);
+
+        // Moving in the opposite direction.
+        expected = arcWithPoint(50, 100, 0, 0, ex, ey);
+        path = arcMotion.getPath(50, 100, 0, 0);
+        assertPathMatches(expected, path);
+    }
+
+    @Test
+    public void testMinimumVerticalAngle() {
+        ArcMotion arcMotion = new ArcMotion();
+        arcMotion.setMinimumVerticalAngle(45);
+        assertEquals(45, arcMotion.getMinimumVerticalAngle(), 0.0f);
+
+        float ex = 0;
+        float ey = 62.5f;
+        Path expected = arcWithPoint(0, 0, 50, 100, ex, ey);
+        Path path = arcMotion.getPath(0, 0, 50, 100);
+        assertPathMatches(expected, path);
+
+        // Pretty much the same, but follows a different path.
+        expected = arcWithPoint(0, 0, 50, 100.001f, ex, ey);
+        path = arcMotion.getPath(0, 0, 50, 100.001f);
+        assertPathMatches(expected, path);
+
+        // Moving in opposite direction.
+        expected = arcWithPoint(50, 100, 0, 0, ex, ey);
+        path = arcMotion.getPath(50, 100, 0, 0);
+        assertPathMatches(expected, path);
+
+        // With x > y.
+        ex = (float) (Math.tan(Math.PI / 4) * 37.5f);
+        ey = 50;
+        expected = arcWithPoint(0, 0, 100, 50, ex, ey);
+        path = arcMotion.getPath(0, 0, 100, 50);
+        assertPathMatches(expected, path);
+
+        // Pretty much the same, but follows a different path.
+        expected = arcWithPoint(0, 0, 100.001f, 50, ex, ey);
+        path = arcMotion.getPath(0, 0, 100.001f, 50);
+        assertPathMatches(expected, path);
+
+        // Moving in opposite direction.
+        expected = arcWithPoint(100, 50, 0, 0, ex, ey);
+        path = arcMotion.getPath(100, 50, 0, 0);
+        assertPathMatches(expected, path);
+
+    }
+
+}
diff --git a/androidx/transition/AutoTransition.java b/androidx/transition/AutoTransition.java
new file mode 100644
index 0000000..e1a077e
--- /dev/null
+++ b/androidx/transition/AutoTransition.java
@@ -0,0 +1,53 @@
+/*
+ * 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 androidx.transition;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * Utility class for creating a default transition that automatically fades,
+ * moves, and resizes views during a scene change.
+ *
+ * <p>An AutoTransition can be described in a resource file by using the
+ * tag <code>autoTransition</code>, along with the other standard
+ * attributes of {@link Transition}.</p>
+ */
+public class AutoTransition extends TransitionSet {
+
+    /**
+     * Constructs an AutoTransition object, which is a TransitionSet which
+     * first fades out disappearing targets, then moves and resizes existing
+     * targets, and finally fades in appearing targets.
+     */
+    public AutoTransition() {
+        init();
+    }
+
+    public AutoTransition(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init();
+    }
+
+    private void init() {
+        setOrdering(ORDERING_SEQUENTIAL);
+        addTransition(new Fade(Fade.OUT))
+                .addTransition(new ChangeBounds())
+                .addTransition(new Fade(Fade.IN));
+    }
+
+}
diff --git a/androidx/transition/AutoTransitionTest.java b/androidx/transition/AutoTransitionTest.java
new file mode 100644
index 0000000..b5df811
--- /dev/null
+++ b/androidx/transition/AutoTransitionTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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 androidx.transition;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import android.graphics.Color;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@MediumTest
+public class AutoTransitionTest extends BaseTest {
+
+    private LinearLayout mRoot;
+    private View mView0;
+    private View mView1;
+
+    @UiThreadTest
+    @Before
+    public void setUp() {
+        mRoot = (LinearLayout) rule.getActivity().getRoot();
+        mView0 = new View(rule.getActivity());
+        mView0.setBackgroundColor(Color.RED);
+        mRoot.addView(mView0, new LinearLayout.LayoutParams(100, 100));
+        mView1 = new View(rule.getActivity());
+        mView1.setBackgroundColor(Color.BLUE);
+        mRoot.addView(mView1, new LinearLayout.LayoutParams(100, 100));
+    }
+
+    @LargeTest
+    @Test
+    public void testLayoutBetweenFadeAndChangeBounds() throws Throwable {
+        final LayoutCounter counter = new LayoutCounter();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertThat(mView1.getY(), is(100.f));
+                assertThat(mView0.getVisibility(), is(View.VISIBLE));
+                mView1.addOnLayoutChangeListener(counter);
+            }
+        });
+        final SyncTransitionListener listener = new SyncTransitionListener(
+                SyncTransitionListener.EVENT_END);
+        final Transition transition = new AutoTransition();
+        transition.addListener(listener);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, transition);
+                // This makes view0 fade out and causes view1 to move upwards.
+                mView0.setVisibility(View.GONE);
+            }
+        });
+        assertThat("Timed out waiting for the TransitionListener",
+                listener.await(), is(true));
+        assertThat(mView1.getY(), is(0.f));
+        assertThat(mView0.getVisibility(), is(View.GONE));
+        counter.reset();
+        listener.reset();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, transition);
+                // Revert
+                mView0.setVisibility(View.VISIBLE);
+            }
+        });
+        assertThat("Timed out waiting for the TransitionListener",
+                listener.await(), is(true));
+        assertThat(mView1.getY(), is(100.f));
+        assertThat(mView0.getVisibility(), is(View.VISIBLE));
+    }
+
+    private static class LayoutCounter implements View.OnLayoutChangeListener {
+
+        private int mCalledCount;
+
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            mCalledCount++;
+            // There should not be more than one layout request to view1.
+            if (mCalledCount > 1) {
+                fail("View layout happened too many times");
+            }
+        }
+
+        void reset() {
+            mCalledCount = 0;
+        }
+
+    }
+
+}
diff --git a/androidx/transition/BaseTest.java b/androidx/transition/BaseTest.java
new file mode 100644
index 0000000..c54b4b3
--- /dev/null
+++ b/androidx/transition/BaseTest.java
@@ -0,0 +1,35 @@
+/*
+ * 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 androidx.transition;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseTest {
+
+    @Rule
+    public final ActivityTestRule<TransitionActivity> rule;
+
+    BaseTest() {
+        rule = new ActivityTestRule<>(TransitionActivity.class);
+    }
+
+}
diff --git a/androidx/transition/BaseTransitionTest.java b/androidx/transition/BaseTransitionTest.java
new file mode 100644
index 0000000..3620100
--- /dev/null
+++ b/androidx/transition/BaseTransitionTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.transition.test.R;
+
+import org.junit.Before;
+
+import java.util.ArrayList;
+
+public abstract class BaseTransitionTest extends BaseTest {
+
+    ArrayList<View> mTransitionTargets = new ArrayList<>();
+    LinearLayout mRoot;
+    Transition mTransition;
+    Transition.TransitionListener mListener;
+    float mAnimatedValue;
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false);
+        mRoot = (LinearLayout) rule.getActivity().findViewById(R.id.root);
+        mTransitionTargets.clear();
+        mTransition = createTransition();
+        mListener = mock(Transition.TransitionListener.class);
+        mTransition.addListener(mListener);
+    }
+
+    Transition createTransition() {
+        return new TestTransition();
+    }
+
+    void waitForStart() {
+        verify(mListener, timeout(3000)).onTransitionStart(any(Transition.class));
+    }
+
+    void waitForEnd() {
+        verify(mListener, timeout(3000)).onTransitionEnd(any(Transition.class));
+    }
+
+    Scene loadScene(final int layoutId) throws Throwable {
+        final Scene[] scene = new Scene[1];
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scene[0] = Scene.getSceneForLayout(mRoot, layoutId, rule.getActivity());
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        return scene[0];
+    }
+
+    void startTransition(final int layoutId) throws Throwable {
+        startTransition(loadScene(layoutId));
+    }
+
+    private void startTransition(final Scene scene) throws Throwable {
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(scene, mTransition);
+            }
+        });
+        waitForStart();
+    }
+
+    void enterScene(final int layoutId) throws Throwable {
+        enterScene(loadScene(layoutId));
+    }
+
+    void enterScene(final Scene scene) throws Throwable {
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                scene.enter();
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    void resetListener() {
+        mTransition.removeListener(mListener);
+        mListener = mock(Transition.TransitionListener.class);
+        mTransition.addListener(mListener);
+    }
+
+    void setAnimatedValue(float animatedValue) {
+        mAnimatedValue = animatedValue;
+    }
+
+    public class TestTransition extends Visibility {
+
+        @Override
+        public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            mTransitionTargets.add(endValues.view);
+            return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 0, 1);
+        }
+
+        @Override
+        public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            mTransitionTargets.add(startValues.view);
+            return ObjectAnimator.ofFloat(BaseTransitionTest.this, "animatedValue", 1, 0);
+        }
+
+    }
+
+}
diff --git a/androidx/transition/ChangeBounds.java b/androidx/transition/ChangeBounds.java
new file mode 100644
index 0000000..fb621ed
--- /dev/null
+++ b/androidx/transition/ChangeBounds.java
@@ -0,0 +1,498 @@
+/*
+ * 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.view.ViewCompat;
+
+import java.util.Map;
+
+/**
+ * This transition captures the layout bounds of target views before and after
+ * the scene change and animates those changes during the transition.
+ *
+ * <p>A ChangeBounds transition can be described in a resource file by using the
+ * tag <code>changeBounds</code>, along with the other standard attributes of Transition.</p>
+ */
+public class ChangeBounds extends Transition {
+
+    private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
+    private static final String PROPNAME_CLIP = "android:changeBounds:clip";
+    private static final String PROPNAME_PARENT = "android:changeBounds:parent";
+    private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
+    private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";
+    private static final String[] sTransitionProperties = {
+            PROPNAME_BOUNDS,
+            PROPNAME_CLIP,
+            PROPNAME_PARENT,
+            PROPNAME_WINDOW_X,
+            PROPNAME_WINDOW_Y
+    };
+
+    private static final Property<Drawable, PointF> DRAWABLE_ORIGIN_PROPERTY =
+            new Property<Drawable, PointF>(PointF.class, "boundsOrigin") {
+                private Rect mBounds = new Rect();
+
+                @Override
+                public void set(Drawable object, PointF value) {
+                    object.copyBounds(mBounds);
+                    mBounds.offsetTo(Math.round(value.x), Math.round(value.y));
+                    object.setBounds(mBounds);
+                }
+
+                @Override
+                public PointF get(Drawable object) {
+                    object.copyBounds(mBounds);
+                    return new PointF(mBounds.left, mBounds.top);
+                }
+            };
+
+    private static final Property<ViewBounds, PointF> TOP_LEFT_PROPERTY =
+            new Property<ViewBounds, PointF>(PointF.class, "topLeft") {
+                @Override
+                public void set(ViewBounds viewBounds, PointF topLeft) {
+                    viewBounds.setTopLeft(topLeft);
+                }
+
+                @Override
+                public PointF get(ViewBounds viewBounds) {
+                    return null;
+                }
+            };
+
+    private static final Property<ViewBounds, PointF> BOTTOM_RIGHT_PROPERTY =
+            new Property<ViewBounds, PointF>(PointF.class, "bottomRight") {
+                @Override
+                public void set(ViewBounds viewBounds, PointF bottomRight) {
+                    viewBounds.setBottomRight(bottomRight);
+                }
+
+                @Override
+                public PointF get(ViewBounds viewBounds) {
+                    return null;
+                }
+            };
+
+    private static final Property<View, PointF> BOTTOM_RIGHT_ONLY_PROPERTY =
+            new Property<View, PointF>(PointF.class, "bottomRight") {
+                @Override
+                public void set(View view, PointF bottomRight) {
+                    int left = view.getLeft();
+                    int top = view.getTop();
+                    int right = Math.round(bottomRight.x);
+                    int bottom = Math.round(bottomRight.y);
+                    ViewUtils.setLeftTopRightBottom(view, left, top, right, bottom);
+                }
+
+                @Override
+                public PointF get(View view) {
+                    return null;
+                }
+            };
+
+    private static final Property<View, PointF> TOP_LEFT_ONLY_PROPERTY =
+            new Property<View, PointF>(PointF.class, "topLeft") {
+                @Override
+                public void set(View view, PointF topLeft) {
+                    int left = Math.round(topLeft.x);
+                    int top = Math.round(topLeft.y);
+                    int right = view.getRight();
+                    int bottom = view.getBottom();
+                    ViewUtils.setLeftTopRightBottom(view, left, top, right, bottom);
+                }
+
+                @Override
+                public PointF get(View view) {
+                    return null;
+                }
+            };
+
+    private static final Property<View, PointF> POSITION_PROPERTY =
+            new Property<View, PointF>(PointF.class, "position") {
+                @Override
+                public void set(View view, PointF topLeft) {
+                    int left = Math.round(topLeft.x);
+                    int top = Math.round(topLeft.y);
+                    int right = left + view.getWidth();
+                    int bottom = top + view.getHeight();
+                    ViewUtils.setLeftTopRightBottom(view, left, top, right, bottom);
+                }
+
+                @Override
+                public PointF get(View view) {
+                    return null;
+                }
+            };
+
+    private int[] mTempLocation = new int[2];
+    private boolean mResizeClip = false;
+    private boolean mReparent = false;
+
+    private static RectEvaluator sRectEvaluator = new RectEvaluator();
+
+    public ChangeBounds() {
+    }
+
+    public ChangeBounds(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.CHANGE_BOUNDS);
+        boolean resizeClip = TypedArrayUtils.getNamedBoolean(a, (XmlResourceParser) attrs,
+                "resizeClip", Styleable.ChangeBounds.RESIZE_CLIP, false);
+        a.recycle();
+        setResizeClip(resizeClip);
+    }
+
+    @Nullable
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    /**
+     * When <code>resizeClip</code> is true, ChangeBounds resizes the view using the clipBounds
+     * instead of changing the dimensions of the view during the animation. When
+     * <code>resizeClip</code> is false, ChangeBounds resizes the View by changing its dimensions.
+     *
+     * <p>When resizeClip is set to true, the clip bounds is modified by ChangeBounds. Therefore,
+     * {@link android.transition.ChangeClipBounds} is not compatible with ChangeBounds
+     * in this mode.</p>
+     *
+     * @param resizeClip Used to indicate whether the view bounds should be modified or the
+     *                   clip bounds should be modified by ChangeBounds.
+     * @see android.view.View#setClipBounds(android.graphics.Rect)
+     */
+    public void setResizeClip(boolean resizeClip) {
+        mResizeClip = resizeClip;
+    }
+
+    /**
+     * Returns true when the ChangeBounds will resize by changing the clip bounds during the
+     * view animation or false when bounds are changed. The default value is false.
+     *
+     * @return true when the ChangeBounds will resize by changing the clip bounds during the
+     * view animation or false when bounds are changed. The default value is false.
+     */
+    public boolean getResizeClip() {
+        return mResizeClip;
+    }
+
+    private void captureValues(TransitionValues values) {
+        View view = values.view;
+
+        if (ViewCompat.isLaidOut(view) || view.getWidth() != 0 || view.getHeight() != 0) {
+            values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
+                    view.getRight(), view.getBottom()));
+            values.values.put(PROPNAME_PARENT, values.view.getParent());
+            if (mReparent) {
+                values.view.getLocationInWindow(mTempLocation);
+                values.values.put(PROPNAME_WINDOW_X, mTempLocation[0]);
+                values.values.put(PROPNAME_WINDOW_Y, mTempLocation[1]);
+            }
+            if (mResizeClip) {
+                values.values.put(PROPNAME_CLIP, ViewCompat.getClipBounds(view));
+            }
+        }
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    private boolean parentMatches(View startParent, View endParent) {
+        boolean parentMatches = true;
+        if (mReparent) {
+            TransitionValues endValues = getMatchedTransitionValues(startParent, true);
+            if (endValues == null) {
+                parentMatches = startParent == endParent;
+            } else {
+                parentMatches = endParent == endValues.view;
+            }
+        }
+        return parentMatches;
+    }
+
+    @Override
+    @Nullable
+    public Animator createAnimator(@NonNull final ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+        if (startValues == null || endValues == null) {
+            return null;
+        }
+        Map<String, Object> startParentVals = startValues.values;
+        Map<String, Object> endParentVals = endValues.values;
+        ViewGroup startParent = (ViewGroup) startParentVals.get(PROPNAME_PARENT);
+        ViewGroup endParent = (ViewGroup) endParentVals.get(PROPNAME_PARENT);
+        if (startParent == null || endParent == null) {
+            return null;
+        }
+        final View view = endValues.view;
+        if (parentMatches(startParent, endParent)) {
+            Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+            Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+            final int startLeft = startBounds.left;
+            final int endLeft = endBounds.left;
+            final int startTop = startBounds.top;
+            final int endTop = endBounds.top;
+            final int startRight = startBounds.right;
+            final int endRight = endBounds.right;
+            final int startBottom = startBounds.bottom;
+            final int endBottom = endBounds.bottom;
+            final int startWidth = startRight - startLeft;
+            final int startHeight = startBottom - startTop;
+            final int endWidth = endRight - endLeft;
+            final int endHeight = endBottom - endTop;
+            Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
+            Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+            int numChanges = 0;
+            if ((startWidth != 0 && startHeight != 0) || (endWidth != 0 && endHeight != 0)) {
+                if (startLeft != endLeft || startTop != endTop) ++numChanges;
+                if (startRight != endRight || startBottom != endBottom) ++numChanges;
+            }
+            if ((startClip != null && !startClip.equals(endClip))
+                    || (startClip == null && endClip != null)) {
+                ++numChanges;
+            }
+            if (numChanges > 0) {
+                Animator anim;
+                if (!mResizeClip) {
+                    ViewUtils.setLeftTopRightBottom(view, startLeft, startTop, startRight,
+                            startBottom);
+                    if (numChanges == 2) {
+                        if (startWidth == endWidth && startHeight == endHeight) {
+                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
+                                    endTop);
+                            anim = ObjectAnimatorUtils.ofPointF(view, POSITION_PROPERTY,
+                                    topLeftPath);
+                        } else {
+                            final ViewBounds viewBounds = new ViewBounds(view);
+                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+                                    endLeft, endTop);
+                            ObjectAnimator topLeftAnimator = ObjectAnimatorUtils
+                                    .ofPointF(viewBounds, TOP_LEFT_PROPERTY, topLeftPath);
+
+                            Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
+                                    endRight, endBottom);
+                            ObjectAnimator bottomRightAnimator = ObjectAnimatorUtils.ofPointF(
+                                    viewBounds, BOTTOM_RIGHT_PROPERTY, bottomRightPath);
+                            AnimatorSet set = new AnimatorSet();
+                            set.playTogether(topLeftAnimator, bottomRightAnimator);
+                            anim = set;
+                            set.addListener(new AnimatorListenerAdapter() {
+                                // We need a strong reference to viewBounds until the
+                                // animator ends (The ObjectAnimator holds only a weak reference).
+                                @SuppressWarnings("unused")
+                                private ViewBounds mViewBounds = viewBounds;
+                            });
+                        }
+                    } else if (startLeft != endLeft || startTop != endTop) {
+                        Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
+                                endLeft, endTop);
+                        anim = ObjectAnimatorUtils.ofPointF(view, TOP_LEFT_ONLY_PROPERTY,
+                                topLeftPath);
+                    } else {
+                        Path bottomRight = getPathMotion().getPath(startRight, startBottom,
+                                endRight, endBottom);
+                        anim = ObjectAnimatorUtils.ofPointF(view, BOTTOM_RIGHT_ONLY_PROPERTY,
+                                bottomRight);
+                    }
+                } else {
+                    int maxWidth = Math.max(startWidth, endWidth);
+                    int maxHeight = Math.max(startHeight, endHeight);
+
+                    ViewUtils.setLeftTopRightBottom(view, startLeft, startTop, startLeft + maxWidth,
+                            startTop + maxHeight);
+
+                    ObjectAnimator positionAnimator = null;
+                    if (startLeft != endLeft || startTop != endTop) {
+                        Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
+                                endTop);
+                        positionAnimator = ObjectAnimatorUtils.ofPointF(view, POSITION_PROPERTY,
+                                topLeftPath);
+                    }
+                    final Rect finalClip = endClip;
+                    if (startClip == null) {
+                        startClip = new Rect(0, 0, startWidth, startHeight);
+                    }
+                    if (endClip == null) {
+                        endClip = new Rect(0, 0, endWidth, endHeight);
+                    }
+                    ObjectAnimator clipAnimator = null;
+                    if (!startClip.equals(endClip)) {
+                        ViewCompat.setClipBounds(view, startClip);
+                        clipAnimator = ObjectAnimator.ofObject(view, "clipBounds", sRectEvaluator,
+                                startClip, endClip);
+                        clipAnimator.addListener(new AnimatorListenerAdapter() {
+                            private boolean mIsCanceled;
+
+                            @Override
+                            public void onAnimationCancel(Animator animation) {
+                                mIsCanceled = true;
+                            }
+
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                if (!mIsCanceled) {
+                                    ViewCompat.setClipBounds(view, finalClip);
+                                    ViewUtils.setLeftTopRightBottom(view, endLeft, endTop, endRight,
+                                            endBottom);
+                                }
+                            }
+                        });
+                    }
+                    anim = TransitionUtils.mergeAnimators(positionAnimator,
+                            clipAnimator);
+                }
+                if (view.getParent() instanceof ViewGroup) {
+                    final ViewGroup parent = (ViewGroup) view.getParent();
+                    ViewGroupUtils.suppressLayout(parent, true);
+                    TransitionListener transitionListener = new TransitionListenerAdapter() {
+                        boolean mCanceled = false;
+
+                        @Override
+                        public void onTransitionCancel(@NonNull Transition transition) {
+                            ViewGroupUtils.suppressLayout(parent, false);
+                            mCanceled = true;
+                        }
+
+                        @Override
+                        public void onTransitionEnd(@NonNull Transition transition) {
+                            if (!mCanceled) {
+                                ViewGroupUtils.suppressLayout(parent, false);
+                            }
+                            transition.removeListener(this);
+                        }
+
+                        @Override
+                        public void onTransitionPause(@NonNull Transition transition) {
+                            ViewGroupUtils.suppressLayout(parent, false);
+                        }
+
+                        @Override
+                        public void onTransitionResume(@NonNull Transition transition) {
+                            ViewGroupUtils.suppressLayout(parent, true);
+                        }
+                    };
+                    addListener(transitionListener);
+                }
+                return anim;
+            }
+        } else {
+            int startX = (Integer) startValues.values.get(PROPNAME_WINDOW_X);
+            int startY = (Integer) startValues.values.get(PROPNAME_WINDOW_Y);
+            int endX = (Integer) endValues.values.get(PROPNAME_WINDOW_X);
+            int endY = (Integer) endValues.values.get(PROPNAME_WINDOW_Y);
+            // TODO: also handle size changes: check bounds and animate size changes
+            if (startX != endX || startY != endY) {
+                sceneRoot.getLocationInWindow(mTempLocation);
+                Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+                        Bitmap.Config.ARGB_8888);
+                Canvas canvas = new Canvas(bitmap);
+                view.draw(canvas);
+                @SuppressWarnings("deprecation") final BitmapDrawable drawable = new BitmapDrawable(
+                        bitmap);
+                final float transitionAlpha = ViewUtils.getTransitionAlpha(view);
+                ViewUtils.setTransitionAlpha(view, 0);
+                ViewUtils.getOverlay(sceneRoot).add(drawable);
+                Path topLeftPath = getPathMotion().getPath(startX - mTempLocation[0],
+                        startY - mTempLocation[1], endX - mTempLocation[0],
+                        endY - mTempLocation[1]);
+                PropertyValuesHolder origin = PropertyValuesHolderUtils.ofPointF(
+                        DRAWABLE_ORIGIN_PROPERTY, topLeftPath);
+                ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(drawable, origin);
+                anim.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        ViewUtils.getOverlay(sceneRoot).remove(drawable);
+                        ViewUtils.setTransitionAlpha(view, transitionAlpha);
+                    }
+                });
+                return anim;
+            }
+        }
+        return null;
+    }
+
+    private static class ViewBounds {
+
+        private int mLeft;
+        private int mTop;
+        private int mRight;
+        private int mBottom;
+        private View mView;
+        private int mTopLeftCalls;
+        private int mBottomRightCalls;
+
+        ViewBounds(View view) {
+            mView = view;
+        }
+
+        void setTopLeft(PointF topLeft) {
+            mLeft = Math.round(topLeft.x);
+            mTop = Math.round(topLeft.y);
+            mTopLeftCalls++;
+            if (mTopLeftCalls == mBottomRightCalls) {
+                setLeftTopRightBottom();
+            }
+        }
+
+        void setBottomRight(PointF bottomRight) {
+            mRight = Math.round(bottomRight.x);
+            mBottom = Math.round(bottomRight.y);
+            mBottomRightCalls++;
+            if (mTopLeftCalls == mBottomRightCalls) {
+                setLeftTopRightBottom();
+            }
+        }
+
+        private void setLeftTopRightBottom() {
+            ViewUtils.setLeftTopRightBottom(mView, mLeft, mTop, mRight, mBottom);
+            mTopLeftCalls = 0;
+            mBottomRightCalls = 0;
+        }
+
+    }
+
+}
diff --git a/androidx/transition/ChangeBoundsTest.java b/androidx/transition/ChangeBoundsTest.java
new file mode 100644
index 0000000..345ad0a
--- /dev/null
+++ b/androidx/transition/ChangeBoundsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.MediumTest;
+import android.view.View;
+import android.view.animation.LinearInterpolator;
+
+import androidx.transition.test.R;
+
+import org.hamcrest.Description;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Test;
+
+@MediumTest
+public class ChangeBoundsTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        final ChangeBounds changeBounds = new ChangeBounds();
+        changeBounds.setDuration(400);
+        changeBounds.setInterpolator(new LinearInterpolator());
+        return changeBounds;
+    }
+
+    @Test
+    public void testResizeClip() {
+        ChangeBounds changeBounds = (ChangeBounds) mTransition;
+        assertThat(changeBounds.getResizeClip(), is(false));
+        changeBounds.setResizeClip(true);
+        assertThat(changeBounds.getResizeClip(), is(true));
+    }
+
+    @Test
+    public void testBasic() throws Throwable {
+        enterScene(R.layout.scene1);
+        final ViewHolder startHolder = new ViewHolder(rule.getActivity());
+        assertThat(startHolder.red, is(atTop()));
+        assertThat(startHolder.green, is(below(startHolder.red)));
+        startTransition(R.layout.scene6);
+        waitForEnd();
+        final ViewHolder endHolder = new ViewHolder(rule.getActivity());
+        assertThat(endHolder.green, is(atTop()));
+        assertThat(endHolder.red, is(below(endHolder.green)));
+    }
+
+    private static TypeSafeMatcher<View> atTop() {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            protected boolean matchesSafely(View view) {
+                return view.getTop() == 0;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("is placed at the top of its parent");
+            }
+        };
+    }
+
+    private static TypeSafeMatcher<View> below(final View other) {
+        return new TypeSafeMatcher<View>() {
+            @Override
+            protected boolean matchesSafely(View item) {
+                return other.getBottom() == item.getTop();
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("is placed below the specified view");
+            }
+        };
+    }
+
+    private static class ViewHolder {
+
+        public final View red;
+        public final View green;
+
+        ViewHolder(TransitionActivity activity) {
+            red = activity.findViewById(R.id.redSquare);
+            green = activity.findViewById(R.id.greenSquare);
+        }
+    }
+
+}
diff --git a/androidx/transition/ChangeClipBounds.java b/androidx/transition/ChangeClipBounds.java
new file mode 100644
index 0000000..8762719
--- /dev/null
+++ b/androidx/transition/ChangeClipBounds.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+
+/**
+ * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the
+ * scene change and animates those changes during the transition.
+ *
+ * <p>Prior to API 18 this does nothing.</p>
+ */
+public class ChangeClipBounds extends Transition {
+
+    private static final String PROPNAME_CLIP = "android:clipBounds:clip";
+    private static final String PROPNAME_BOUNDS = "android:clipBounds:bounds";
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_CLIP,
+    };
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    public ChangeClipBounds() {
+    }
+
+    public ChangeClipBounds(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void captureValues(TransitionValues values) {
+        View view = values.view;
+        if (view.getVisibility() == View.GONE) {
+            return;
+        }
+
+        Rect clip = ViewCompat.getClipBounds(view);
+        values.values.put(PROPNAME_CLIP, clip);
+        if (clip == null) {
+            Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
+            values.values.put(PROPNAME_BOUNDS, bounds);
+        }
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public Animator createAnimator(@NonNull final ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null
+                || !startValues.values.containsKey(PROPNAME_CLIP)
+                || !endValues.values.containsKey(PROPNAME_CLIP)) {
+            return null;
+        }
+        Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
+        Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
+        final boolean endIsNull = end == null;
+        if (start == null && end == null) {
+            return null; // No animation required since there is no clip.
+        }
+
+        if (start == null) {
+            start = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+        } else if (end == null) {
+            end = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        }
+        if (start.equals(end)) {
+            return null;
+        }
+
+        ViewCompat.setClipBounds(endValues.view, start);
+        RectEvaluator evaluator = new RectEvaluator(new Rect());
+        ObjectAnimator animator = ObjectAnimator.ofObject(endValues.view, ViewUtils.CLIP_BOUNDS,
+                evaluator, start, end);
+        if (endIsNull) {
+            final View endView = endValues.view;
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    ViewCompat.setClipBounds(endView, null);
+                }
+            });
+        }
+        return animator;
+    }
+}
diff --git a/androidx/transition/ChangeClipBoundsTest.java b/androidx/transition/ChangeClipBoundsTest.java
new file mode 100644
index 0000000..7a416b2
--- /dev/null
+++ b/androidx/transition/ChangeClipBoundsTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.support.test.filters.SdkSuppress;
+import android.view.View;
+
+import androidx.core.view.ViewCompat;
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class ChangeClipBoundsTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new ChangeClipBounds();
+    }
+
+    @SdkSuppress(minSdkVersion = 18)
+    @Test
+    public void testChangeClipBounds() throws Throwable {
+        enterScene(R.layout.scene1);
+
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final Rect newClip = new Rect(redSquare.getLeft() + 10, redSquare.getTop() + 10,
+                redSquare.getRight() - 10, redSquare.getBottom() - 10);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertNull(ViewCompat.getClipBounds(redSquare));
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                ViewCompat.setClipBounds(redSquare, newClip);
+            }
+        });
+        waitForStart();
+        Thread.sleep(150);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Rect midClip = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(midClip);
+                assertTrue(midClip.left > 0 && midClip.left < newClip.left);
+                assertTrue(midClip.top > 0 && midClip.top < newClip.top);
+                assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
+                assertTrue(midClip.bottom < redSquare.getBottom()
+                        && midClip.bottom > newClip.bottom);
+            }
+        });
+        waitForEnd();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final Rect endRect = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(endRect);
+                assertEquals(newClip, endRect);
+            }
+        });
+
+        resetListener();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                ViewCompat.setClipBounds(redSquare, null);
+            }
+        });
+        waitForStart();
+        Thread.sleep(150);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Rect midClip = ViewCompat.getClipBounds(redSquare);
+                assertNotNull(midClip);
+                assertTrue(midClip.left > 0 && midClip.left < newClip.left);
+                assertTrue(midClip.top > 0 && midClip.top < newClip.top);
+                assertTrue(midClip.right < redSquare.getRight() && midClip.right > newClip.right);
+                assertTrue(midClip.bottom < redSquare.getBottom()
+                        && midClip.bottom > newClip.bottom);
+            }
+        });
+        waitForEnd();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertNull(ViewCompat.getClipBounds(redSquare));
+            }
+        });
+
+    }
+
+    @Test
+    public void dummy() {
+        // Avoid "No tests found" on older devices
+    }
+
+}
diff --git a/androidx/transition/ChangeImageTransform.java b/androidx/transition/ChangeImageTransform.java
new file mode 100644
index 0000000..2830097
--- /dev/null
+++ b/androidx/transition/ChangeImageTransform.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+
+import java.util.Map;
+
+/**
+ * This Transition captures an ImageView's matrix before and after the
+ * scene change and animates it during the transition.
+ *
+ * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
+ * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
+ * smoothly.</p>
+ */
+public class ChangeImageTransform extends Transition {
+
+    private static final String PROPNAME_MATRIX = "android:changeImageTransform:matrix";
+    private static final String PROPNAME_BOUNDS = "android:changeImageTransform:bounds";
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_MATRIX,
+            PROPNAME_BOUNDS,
+    };
+
+    private static final TypeEvaluator<Matrix> NULL_MATRIX_EVALUATOR = new TypeEvaluator<Matrix>() {
+        @Override
+        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+            return null;
+        }
+    };
+
+    private static final Property<ImageView, Matrix> ANIMATED_TRANSFORM_PROPERTY =
+            new Property<ImageView, Matrix>(Matrix.class, "animatedTransform") {
+                @Override
+                public void set(ImageView view, Matrix matrix) {
+                    ImageViewUtils.animateTransform(view, matrix);
+                }
+
+                @Override
+                public Matrix get(ImageView object) {
+                    return null;
+                }
+            };
+
+    public ChangeImageTransform() {
+    }
+
+    public ChangeImageTransform(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
+            return;
+        }
+        ImageView imageView = (ImageView) view;
+        Drawable drawable = imageView.getDrawable();
+        if (drawable == null) {
+            return;
+        }
+        Map<String, Object> values = transitionValues.values;
+
+        int left = view.getLeft();
+        int top = view.getTop();
+        int right = view.getRight();
+        int bottom = view.getBottom();
+
+        Rect bounds = new Rect(left, top, right, bottom);
+        values.put(PROPNAME_BOUNDS, bounds);
+        values.put(PROPNAME_MATRIX, copyImageMatrix(imageView));
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    /**
+     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
+     * {@link android.widget.ImageView.ScaleType}.
+     *
+     * @param sceneRoot   The root of the transition hierarchy.
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return An Animator to move an ImageView or null if the View is not an ImageView,
+     * the Drawable changed, the View is not VISIBLE, or there was no change.
+     */
+    @Override
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
+            final TransitionValues endValues) {
+        if (startValues == null || endValues == null) {
+            return null;
+        }
+        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
+        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
+        if (startBounds == null || endBounds == null) {
+            return null;
+        }
+
+        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+        boolean matricesEqual = (startMatrix == null && endMatrix == null)
+                || (startMatrix != null && startMatrix.equals(endMatrix));
+
+        if (startBounds.equals(endBounds) && matricesEqual) {
+            return null;
+        }
+
+        final ImageView imageView = (ImageView) endValues.view;
+        Drawable drawable = imageView.getDrawable();
+        int drawableWidth = drawable.getIntrinsicWidth();
+        int drawableHeight = drawable.getIntrinsicHeight();
+
+        ImageViewUtils.startAnimateTransform(imageView);
+
+        ObjectAnimator animator;
+        if (drawableWidth == 0 || drawableHeight == 0) {
+            animator = createNullAnimator(imageView);
+        } else {
+            if (startMatrix == null) {
+                startMatrix = MatrixUtils.IDENTITY_MATRIX;
+            }
+            if (endMatrix == null) {
+                endMatrix = MatrixUtils.IDENTITY_MATRIX;
+            }
+            ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
+            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
+        }
+
+        ImageViewUtils.reserveEndAnimateTransform(imageView, animator);
+
+        return animator;
+    }
+
+    private ObjectAnimator createNullAnimator(ImageView imageView) {
+        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
+                NULL_MATRIX_EVALUATOR, null, null);
+    }
+
+    private ObjectAnimator createMatrixAnimator(final ImageView imageView, Matrix startMatrix,
+            final Matrix endMatrix) {
+        return ObjectAnimator.ofObject(imageView, ANIMATED_TRANSFORM_PROPERTY,
+                new TransitionUtils.MatrixEvaluator(), startMatrix, endMatrix);
+    }
+
+    private static Matrix copyImageMatrix(ImageView view) {
+        switch (view.getScaleType()) {
+            case FIT_XY:
+                return fitXYMatrix(view);
+            case CENTER_CROP:
+                return centerCropMatrix(view);
+            default:
+                return new Matrix(view.getImageMatrix());
+        }
+    }
+
+    /**
+     * Calculates the image transformation matrix for an ImageView with ScaleType FIT_XY. This
+     * needs to be manually calculated as the platform does not give us the value for this case.
+     */
+    private static Matrix fitXYMatrix(ImageView view) {
+        final Drawable image = view.getDrawable();
+        final Matrix matrix = new Matrix();
+        matrix.postScale(
+                ((float) view.getWidth()) / image.getIntrinsicWidth(),
+                ((float) view.getHeight()) / image.getIntrinsicHeight());
+        return matrix;
+    }
+
+    /**
+     * Calculates the image transformation matrix for an ImageView with ScaleType CENTER_CROP. This
+     * needs to be manually calculated for consistent behavior across all the API levels.
+     */
+    private static Matrix centerCropMatrix(ImageView view) {
+        final Drawable image = view.getDrawable();
+        final int imageWidth = image.getIntrinsicWidth();
+        final int imageViewWidth = view.getWidth();
+        final float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        final int imageHeight = image.getIntrinsicHeight();
+        final int imageViewHeight = view.getHeight();
+        final float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        final float maxScale = Math.max(scaleX, scaleY);
+
+        final float width = imageWidth * maxScale;
+        final float height = imageHeight * maxScale;
+        final int tx = Math.round((imageViewWidth - width) / 2f);
+        final int ty = Math.round((imageViewHeight - height) / 2f);
+
+        final Matrix matrix = new Matrix();
+        matrix.postScale(maxScale, maxScale);
+        matrix.postTranslate(tx, ty);
+        return matrix;
+    }
+
+}
diff --git a/androidx/transition/ChangeImageTransformTest.java b/androidx/transition/ChangeImageTransformTest.java
new file mode 100644
index 0000000..5879976
--- /dev/null
+++ b/androidx/transition/ChangeImageTransformTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.ActivityCompat;
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class ChangeImageTransformTest extends BaseTransitionTest {
+
+    private ChangeImageTransform mChangeImageTransform;
+    private Matrix mStartMatrix;
+    private Matrix mEndMatrix;
+    private Drawable mImage;
+    private ImageView mImageView;
+
+    @Override
+    Transition createTransition() {
+        mChangeImageTransform = new CaptureMatrix();
+        mChangeImageTransform.setDuration(100);
+        mTransition = mChangeImageTransform;
+        resetListener();
+        return mChangeImageTransform;
+    }
+
+    @Test
+    public void testCenterToFitXY() throws Throwable {
+        transformImage(ImageView.ScaleType.CENTER, ImageView.ScaleType.FIT_XY);
+        verifyMatrixMatches(centerMatrix(), mStartMatrix);
+        verifyMatrixMatches(fitXYMatrix(), mEndMatrix);
+    }
+
+    @Test
+    public void testCenterCropToFitCenter() throws Throwable {
+        transformImage(ImageView.ScaleType.CENTER_CROP, ImageView.ScaleType.FIT_CENTER);
+        verifyMatrixMatches(centerCropMatrix(), mStartMatrix);
+        verifyMatrixMatches(fitCenterMatrix(), mEndMatrix);
+    }
+
+    @Test
+    public void testCenterInsideToFitEnd() throws Throwable {
+        transformImage(ImageView.ScaleType.CENTER_INSIDE, ImageView.ScaleType.FIT_END);
+        // CENTER_INSIDE and CENTER are the same when the image is smaller than the View
+        verifyMatrixMatches(centerMatrix(), mStartMatrix);
+        verifyMatrixMatches(fitEndMatrix(), mEndMatrix);
+    }
+
+    @Test
+    public void testFitStartToCenter() throws Throwable {
+        transformImage(ImageView.ScaleType.FIT_START, ImageView.ScaleType.CENTER);
+        verifyMatrixMatches(fitStartMatrix(), mStartMatrix);
+        verifyMatrixMatches(centerMatrix(), mEndMatrix);
+    }
+
+    private Matrix centerMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float tx = Math.round((imageViewWidth - imageWidth) / 2f);
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float ty = Math.round((imageViewHeight - imageHeight) / 2f);
+
+        Matrix matrix = new Matrix();
+        matrix.postTranslate(tx, ty);
+        return matrix;
+    }
+
+    private Matrix fitXYMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(scaleX, scaleY);
+        return matrix;
+    }
+
+    private Matrix centerCropMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        float maxScale = Math.max(scaleX, scaleY);
+
+        float width = imageWidth * maxScale;
+        float height = imageHeight * maxScale;
+        int tx = Math.round((imageViewWidth - width) / 2f);
+        int ty = Math.round((imageViewHeight - height) / 2f);
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(maxScale, maxScale);
+        matrix.postTranslate(tx, ty);
+        return matrix;
+    }
+
+    private Matrix fitCenterMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        float minScale = Math.min(scaleX, scaleY);
+
+        float width = imageWidth * minScale;
+        float height = imageHeight * minScale;
+        float tx = (imageViewWidth - width) / 2f;
+        float ty = (imageViewHeight - height) / 2f;
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(minScale, minScale);
+        matrix.postTranslate(tx, ty);
+        return matrix;
+    }
+
+    private Matrix fitStartMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        float minScale = Math.min(scaleX, scaleY);
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(minScale, minScale);
+        return matrix;
+    }
+
+    private Matrix fitEndMatrix() {
+        int imageWidth = mImage.getIntrinsicWidth();
+        int imageViewWidth = mImageView.getWidth();
+        float scaleX = ((float) imageViewWidth) / imageWidth;
+
+        int imageHeight = mImage.getIntrinsicHeight();
+        int imageViewHeight = mImageView.getHeight();
+        float scaleY = ((float) imageViewHeight) / imageHeight;
+
+        float minScale = Math.min(scaleX, scaleY);
+
+        float width = imageWidth * minScale;
+        float height = imageHeight * minScale;
+        float tx = imageViewWidth - width;
+        float ty = imageViewHeight - height;
+
+        Matrix matrix = new Matrix();
+        matrix.postScale(minScale, minScale);
+        matrix.postTranslate(tx, ty);
+        return matrix;
+    }
+
+    private void verifyMatrixMatches(Matrix expected, Matrix matrix) {
+        if (expected == null) {
+            assertNull(matrix);
+            return;
+        }
+        assertNotNull(matrix);
+        float[] expectedValues = new float[9];
+        expected.getValues(expectedValues);
+
+        float[] values = new float[9];
+        matrix.getValues(values);
+
+        for (int i = 0; i < values.length; i++) {
+            final float expectedValue = expectedValues[i];
+            final float value = values[i];
+            assertEquals("Value [" + i + "]", expectedValue, value, 0.01f);
+        }
+    }
+
+    private void transformImage(ImageView.ScaleType startScale, final ImageView.ScaleType endScale)
+            throws Throwable {
+        final ImageView imageView = enterImageViewScene(startScale);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mChangeImageTransform);
+                imageView.setScaleType(endScale);
+            }
+        });
+        waitForStart();
+        verify(mListener, (startScale == endScale) ? times(1) : never())
+                .onTransitionEnd(any(Transition.class));
+        waitForEnd();
+    }
+
+    private ImageView enterImageViewScene(final ImageView.ScaleType scaleType) throws Throwable {
+        enterScene(R.layout.scene4);
+        final ViewGroup container = (ViewGroup) rule.getActivity().findViewById(R.id.holder);
+        final ImageView[] imageViews = new ImageView[1];
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mImageView = new ImageView(rule.getActivity());
+                mImage = ActivityCompat.getDrawable(rule.getActivity(),
+                        android.R.drawable.ic_media_play);
+                mImageView.setImageDrawable(mImage);
+                mImageView.setScaleType(scaleType);
+                imageViews[0] = mImageView;
+                container.addView(mImageView);
+                ViewGroup.LayoutParams layoutParams = mImageView.getLayoutParams();
+                DisplayMetrics metrics = rule.getActivity().getResources().getDisplayMetrics();
+                float size = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, metrics);
+                layoutParams.width = Math.round(size);
+                layoutParams.height = Math.round(size * 2);
+                mImageView.setLayoutParams(layoutParams);
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        return imageViews[0];
+    }
+
+    private class CaptureMatrix extends ChangeImageTransform {
+
+        @Override
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
+            assertNotNull(animator);
+            animator.addListener(new CaptureMatrixListener((ImageView) endValues.view));
+            return animator;
+        }
+
+    }
+
+    private class CaptureMatrixListener extends AnimatorListenerAdapter {
+
+        private final ImageView mImageView;
+
+        CaptureMatrixListener(ImageView view) {
+            mImageView = view;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            mStartMatrix = copyMatrix();
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mEndMatrix = copyMatrix();
+        }
+
+        private Matrix copyMatrix() {
+            Matrix matrix = mImageView.getImageMatrix();
+            if (matrix != null) {
+                matrix = new Matrix(matrix);
+            }
+            return matrix;
+        }
+
+    }
+
+}
diff --git a/androidx/transition/ChangeScroll.java b/androidx/transition/ChangeScroll.java
new file mode 100644
index 0000000..bdf918f
--- /dev/null
+++ b/androidx/transition/ChangeScroll.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+
+/**
+ * This transition captures the scroll properties of targets before and after
+ * the scene change and animates any changes.
+ */
+public class ChangeScroll extends Transition {
+
+    private static final String PROPNAME_SCROLL_X = "android:changeScroll:x";
+    private static final String PROPNAME_SCROLL_Y = "android:changeScroll:y";
+
+    private static final String[] PROPERTIES = {
+            PROPNAME_SCROLL_X,
+            PROPNAME_SCROLL_Y,
+    };
+
+    public ChangeScroll() {}
+
+    public ChangeScroll(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Nullable
+    @Override
+    public String[] getTransitionProperties() {
+        return PROPERTIES;
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        transitionValues.values.put(PROPNAME_SCROLL_X, transitionValues.view.getScrollX());
+        transitionValues.values.put(PROPNAME_SCROLL_Y, transitionValues.view.getScrollY());
+    }
+
+    @Nullable
+    @Override
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+        if (startValues == null || endValues == null) {
+            return null;
+        }
+        final View view = endValues.view;
+        int startX = (Integer) startValues.values.get(PROPNAME_SCROLL_X);
+        int endX = (Integer) endValues.values.get(PROPNAME_SCROLL_X);
+        int startY = (Integer) startValues.values.get(PROPNAME_SCROLL_Y);
+        int endY = (Integer) endValues.values.get(PROPNAME_SCROLL_Y);
+        Animator scrollXAnimator = null;
+        Animator scrollYAnimator = null;
+        if (startX != endX) {
+            view.setScrollX(startX);
+            scrollXAnimator = ObjectAnimator.ofInt(view, "scrollX", startX, endX);
+        }
+        if (startY != endY) {
+            view.setScrollY(startY);
+            scrollYAnimator = ObjectAnimator.ofInt(view, "scrollY", startY, endY);
+        }
+        return TransitionUtils.mergeAnimators(scrollXAnimator, scrollYAnimator);
+    }
+
+}
diff --git a/androidx/transition/ChangeScrollTest.java b/androidx/transition/ChangeScrollTest.java
new file mode 100644
index 0000000..4a01d16
--- /dev/null
+++ b/androidx/transition/ChangeScrollTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.support.test.filters.MediumTest;
+import android.view.View;
+
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class ChangeScrollTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new ChangeScroll();
+    }
+
+    @Test
+    public void testChangeScroll() throws Throwable {
+        enterScene(R.layout.scene5);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final View view = rule.getActivity().findViewById(R.id.text);
+                assertEquals(0, view.getScrollX());
+                assertEquals(0, view.getScrollY());
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                view.scrollTo(150, 300);
+            }
+        });
+        waitForStart();
+        Thread.sleep(150);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final View view = rule.getActivity().findViewById(R.id.text);
+                final int scrollX = view.getScrollX();
+                final int scrollY = view.getScrollY();
+                assertThat(scrollX, is(both(greaterThan(0)).and(lessThan(150))));
+                assertThat(scrollY, is(both(greaterThan(0)).and(lessThan(300))));
+            }
+        });
+        waitForEnd();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                final View view = rule.getActivity().findViewById(R.id.text);
+                assertEquals(150, view.getScrollX());
+                assertEquals(300, view.getScrollY());
+            }
+        });
+    }
+
+}
diff --git a/androidx/transition/ChangeTransform.java b/androidx/transition/ChangeTransform.java
new file mode 100644
index 0000000..c91c16b
--- /dev/null
+++ b/androidx/transition/ChangeTransform.java
@@ -0,0 +1,584 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.view.ViewCompat;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * This Transition captures scale and rotation for Views before and after the
+ * scene change and animates those changes during the transition.
+ *
+ * A change in parent is handled as well by capturing the transforms from
+ * the parent before and after the scene change and animating those during the
+ * transition.
+ */
+public class ChangeTransform extends Transition {
+
+    private static final String PROPNAME_MATRIX = "android:changeTransform:matrix";
+    private static final String PROPNAME_TRANSFORMS = "android:changeTransform:transforms";
+    private static final String PROPNAME_PARENT = "android:changeTransform:parent";
+    private static final String PROPNAME_PARENT_MATRIX = "android:changeTransform:parentMatrix";
+    private static final String PROPNAME_INTERMEDIATE_PARENT_MATRIX =
+            "android:changeTransform:intermediateParentMatrix";
+    private static final String PROPNAME_INTERMEDIATE_MATRIX =
+            "android:changeTransform:intermediateMatrix";
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_MATRIX,
+            PROPNAME_TRANSFORMS,
+            PROPNAME_PARENT_MATRIX,
+    };
+
+    /**
+     * This property sets the animation matrix properties that are not translations.
+     */
+    private static final Property<PathAnimatorMatrix, float[]> NON_TRANSLATIONS_PROPERTY =
+            new Property<PathAnimatorMatrix, float[]>(float[].class, "nonTranslations") {
+                @Override
+                public float[] get(PathAnimatorMatrix object) {
+                    return null;
+                }
+
+                @Override
+                public void set(PathAnimatorMatrix object, float[] value) {
+                    object.setValues(value);
+                }
+            };
+
+    /**
+     * This property sets the translation animation matrix properties.
+     */
+    private static final Property<PathAnimatorMatrix, PointF> TRANSLATIONS_PROPERTY =
+            new Property<PathAnimatorMatrix, PointF>(PointF.class, "translations") {
+                @Override
+                public PointF get(PathAnimatorMatrix object) {
+                    return null;
+                }
+
+                @Override
+                public void set(PathAnimatorMatrix object, PointF value) {
+                    object.setTranslation(value);
+                }
+            };
+
+    /**
+     * Newer platforms suppress view removal at the beginning of the animation.
+     */
+    private static final boolean SUPPORTS_VIEW_REMOVAL_SUPPRESSION = Build.VERSION.SDK_INT >= 21;
+
+    private boolean mUseOverlay = true;
+    private boolean mReparent = true;
+    private Matrix mTempMatrix = new Matrix();
+
+    public ChangeTransform() {
+    }
+
+    public ChangeTransform(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.CHANGE_TRANSFORM);
+        mUseOverlay = TypedArrayUtils.getNamedBoolean(a, (XmlPullParser) attrs,
+                "reparentWithOverlay", Styleable.ChangeTransform.REPARENT_WITH_OVERLAY, true);
+        mReparent = TypedArrayUtils.getNamedBoolean(a, (XmlPullParser) attrs,
+                "reparent", Styleable.ChangeTransform.REPARENT, true);
+        a.recycle();
+    }
+
+    /**
+     * Returns whether changes to parent should use an overlay or not. When the parent
+     * change doesn't use an overlay, it affects the transforms of the child. The
+     * default value is <code>true</code>.
+     *
+     * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when
+     * it moves outside the bounds of its parent. Setting
+     * {@link android.view.ViewGroup#setClipChildren(boolean)} and
+     * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when
+     * Overlays are not used and the parent is animating its location, the position of the
+     * child view will be relative to its parent's final position, so it may appear to "jump"
+     * at the beginning.</p>
+     *
+     * @return <code>true</code> when a changed parent should execute the transition
+     * inside the scene root's overlay or <code>false</code> if a parent change only
+     * affects the transform of the transitioning view.
+     */
+    public boolean getReparentWithOverlay() {
+        return mUseOverlay;
+    }
+
+    /**
+     * Sets whether changes to parent should use an overlay or not. When the parent
+     * change doesn't use an overlay, it affects the transforms of the child. The
+     * default value is <code>true</code>.
+     *
+     * <p>Note: when Overlays are not used when a parent changes, a view can be clipped when
+     * it moves outside the bounds of its parent. Setting
+     * {@link android.view.ViewGroup#setClipChildren(boolean)} and
+     * {@link android.view.ViewGroup#setClipToPadding(boolean)} can help. Also, when
+     * Overlays are not used and the parent is animating its location, the position of the
+     * child view will be relative to its parent's final position, so it may appear to "jump"
+     * at the beginning.</p>
+     *
+     * @param reparentWithOverlay <code>true</code> when a changed parent should execute the
+     *                            transition inside the scene root's overlay or <code>false</code>
+     *                            if a parent change only affects the transform of the
+     *                            transitioning view.
+     */
+    public void setReparentWithOverlay(boolean reparentWithOverlay) {
+        mUseOverlay = reparentWithOverlay;
+    }
+
+    /**
+     * Returns whether parent changes will be tracked by the ChangeTransform. If parent
+     * changes are tracked, then the transform will adjust to the transforms of the
+     * different parents. If they aren't tracked, only the transforms of the transitioning
+     * view will be tracked. Default is true.
+     *
+     * @return whether parent changes will be tracked by the ChangeTransform.
+     */
+    public boolean getReparent() {
+        return mReparent;
+    }
+
+    /**
+     * Sets whether parent changes will be tracked by the ChangeTransform. If parent
+     * changes are tracked, then the transform will adjust to the transforms of the
+     * different parents. If they aren't tracked, only the transforms of the transitioning
+     * view will be tracked. Default is true.
+     *
+     * @param reparent Set to true to track parent changes or false to only track changes
+     *                 of the transitioning view without considering the parent change.
+     */
+    public void setReparent(boolean reparent) {
+        mReparent = reparent;
+    }
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        if (view.getVisibility() == View.GONE) {
+            return;
+        }
+        transitionValues.values.put(PROPNAME_PARENT, view.getParent());
+        Transforms transforms = new Transforms(view);
+        transitionValues.values.put(PROPNAME_TRANSFORMS, transforms);
+        Matrix matrix = view.getMatrix();
+        if (matrix == null || matrix.isIdentity()) {
+            matrix = null;
+        } else {
+            matrix = new Matrix(matrix);
+        }
+        transitionValues.values.put(PROPNAME_MATRIX, matrix);
+        if (mReparent) {
+            Matrix parentMatrix = new Matrix();
+            ViewGroup parent = (ViewGroup) view.getParent();
+            ViewUtils.transformMatrixToGlobal(parent, parentMatrix);
+            parentMatrix.preTranslate(-parent.getScrollX(), -parent.getScrollY());
+            transitionValues.values.put(PROPNAME_PARENT_MATRIX, parentMatrix);
+            transitionValues.values.put(PROPNAME_INTERMEDIATE_MATRIX,
+                    view.getTag(R.id.transition_transform));
+            transitionValues.values.put(PROPNAME_INTERMEDIATE_PARENT_MATRIX,
+                    view.getTag(R.id.parent_matrix));
+        }
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+        if (!SUPPORTS_VIEW_REMOVAL_SUPPRESSION) {
+            // We still don't know if the view is removed or not, but we need to do this here, or
+            // the view will be actually removed, resulting in flickering at the beginning of the
+            // animation. We are canceling this afterwards.
+            ((ViewGroup) transitionValues.view.getParent()).startViewTransition(
+                    transitionValues.view);
+        }
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null
+                || !startValues.values.containsKey(PROPNAME_PARENT)
+                || !endValues.values.containsKey(PROPNAME_PARENT)) {
+            return null;
+        }
+
+        ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
+        ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
+        boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent);
+
+        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_INTERMEDIATE_MATRIX);
+        if (startMatrix != null) {
+            startValues.values.put(PROPNAME_MATRIX, startMatrix);
+        }
+
+        Matrix startParentMatrix = (Matrix)
+                startValues.values.get(PROPNAME_INTERMEDIATE_PARENT_MATRIX);
+        if (startParentMatrix != null) {
+            startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix);
+        }
+
+        // First handle the parent change:
+        if (handleParentChange) {
+            setMatricesForParent(startValues, endValues);
+        }
+
+        // Next handle the normal matrix transform:
+        ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues,
+                handleParentChange);
+
+        if (handleParentChange && transformAnimator != null && mUseOverlay) {
+            createGhostView(sceneRoot, startValues, endValues);
+        } else if (!SUPPORTS_VIEW_REMOVAL_SUPPRESSION) {
+            // We didn't need to suppress the view removal in this case. Cancel the suppression.
+            startParent.endViewTransition(startValues.view);
+        }
+
+        return transformAnimator;
+    }
+
+    private ObjectAnimator createTransformAnimator(TransitionValues startValues,
+            TransitionValues endValues, final boolean handleParentChange) {
+        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+        if (startMatrix == null) {
+            startMatrix = MatrixUtils.IDENTITY_MATRIX;
+        }
+
+        if (endMatrix == null) {
+            endMatrix = MatrixUtils.IDENTITY_MATRIX;
+        }
+
+        if (startMatrix.equals(endMatrix)) {
+            return null;
+        }
+
+        final Transforms transforms = (Transforms) endValues.values.get(PROPNAME_TRANSFORMS);
+
+        // clear the transform properties so that we can use the animation matrix instead
+        final View view = endValues.view;
+        setIdentityTransforms(view);
+
+        final float[] startMatrixValues = new float[9];
+        startMatrix.getValues(startMatrixValues);
+        final float[] endMatrixValues = new float[9];
+        endMatrix.getValues(endMatrixValues);
+        final PathAnimatorMatrix pathAnimatorMatrix =
+                new PathAnimatorMatrix(view, startMatrixValues);
+
+        PropertyValuesHolder valuesProperty = PropertyValuesHolder.ofObject(
+                NON_TRANSLATIONS_PROPERTY, new FloatArrayEvaluator(new float[9]),
+                startMatrixValues, endMatrixValues);
+        Path path = getPathMotion().getPath(startMatrixValues[Matrix.MTRANS_X],
+                startMatrixValues[Matrix.MTRANS_Y], endMatrixValues[Matrix.MTRANS_X],
+                endMatrixValues[Matrix.MTRANS_Y]);
+        PropertyValuesHolder translationProperty = PropertyValuesHolderUtils.ofPointF(
+                TRANSLATIONS_PROPERTY, path);
+        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(pathAnimatorMatrix,
+                valuesProperty, translationProperty);
+
+        final Matrix finalEndMatrix = endMatrix;
+
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            private boolean mIsCanceled;
+            private Matrix mTempMatrix = new Matrix();
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mIsCanceled = true;
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!mIsCanceled) {
+                    if (handleParentChange && mUseOverlay) {
+                        setCurrentMatrix(finalEndMatrix);
+                    } else {
+                        view.setTag(R.id.transition_transform, null);
+                        view.setTag(R.id.parent_matrix, null);
+                    }
+                }
+                ViewUtils.setAnimationMatrix(view, null);
+                transforms.restore(view);
+            }
+
+            @Override
+            public void onAnimationPause(Animator animation) {
+                Matrix currentMatrix = pathAnimatorMatrix.getMatrix();
+                setCurrentMatrix(currentMatrix);
+            }
+
+            @Override
+            public void onAnimationResume(Animator animation) {
+                setIdentityTransforms(view);
+            }
+
+            private void setCurrentMatrix(Matrix currentMatrix) {
+                mTempMatrix.set(currentMatrix);
+                view.setTag(R.id.transition_transform, mTempMatrix);
+                transforms.restore(view);
+            }
+        };
+
+        animator.addListener(listener);
+        AnimatorUtils.addPauseListener(animator, listener);
+        return animator;
+    }
+
+    private boolean parentsMatch(ViewGroup startParent, ViewGroup endParent) {
+        boolean parentsMatch = false;
+        if (!isValidTarget(startParent) || !isValidTarget(endParent)) {
+            parentsMatch = startParent == endParent;
+        } else {
+            TransitionValues endValues = getMatchedTransitionValues(startParent, true);
+            if (endValues != null) {
+                parentsMatch = endParent == endValues.view;
+            }
+        }
+        return parentsMatch;
+    }
+
+    private void createGhostView(final ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        View view = endValues.view;
+
+        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX);
+        Matrix localEndMatrix = new Matrix(endMatrix);
+        ViewUtils.transformMatrixToLocal(sceneRoot, localEndMatrix);
+
+        GhostViewImpl ghostView = GhostViewUtils.addGhost(view, sceneRoot, localEndMatrix);
+        if (ghostView == null) {
+            return;
+        }
+        // Ask GhostView to actually remove the start view when it starts drawing the animation.
+        ghostView.reserveEndViewTransition((ViewGroup) startValues.values.get(PROPNAME_PARENT),
+                startValues.view);
+
+        Transition outerTransition = this;
+        while (outerTransition.mParent != null) {
+            outerTransition = outerTransition.mParent;
+        }
+
+        GhostListener listener = new GhostListener(view, ghostView);
+        outerTransition.addListener(listener);
+
+        // We cannot do this for older platforms or it invalidates the view and results in
+        // flickering, but the view will still be invisible by actually removing it from the parent.
+        if (SUPPORTS_VIEW_REMOVAL_SUPPRESSION) {
+            if (startValues.view != endValues.view) {
+                ViewUtils.setTransitionAlpha(startValues.view, 0);
+            }
+            ViewUtils.setTransitionAlpha(view, 1);
+        }
+    }
+
+    private void setMatricesForParent(TransitionValues startValues, TransitionValues endValues) {
+        Matrix endParentMatrix = (Matrix) endValues.values.get(PROPNAME_PARENT_MATRIX);
+        endValues.view.setTag(R.id.parent_matrix, endParentMatrix);
+
+        Matrix toLocal = mTempMatrix;
+        toLocal.reset();
+        endParentMatrix.invert(toLocal);
+
+        Matrix startLocal = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+        if (startLocal == null) {
+            startLocal = new Matrix();
+            startValues.values.put(PROPNAME_MATRIX, startLocal);
+        }
+
+        Matrix startParentMatrix = (Matrix) startValues.values.get(PROPNAME_PARENT_MATRIX);
+        startLocal.postConcat(startParentMatrix);
+        startLocal.postConcat(toLocal);
+    }
+
+    private static void setIdentityTransforms(View view) {
+        setTransforms(view, 0, 0, 0, 1, 1, 0, 0, 0);
+    }
+
+    private static void setTransforms(View view, float translationX, float translationY,
+            float translationZ, float scaleX, float scaleY, float rotationX,
+            float rotationY, float rotationZ) {
+        view.setTranslationX(translationX);
+        view.setTranslationY(translationY);
+        ViewCompat.setTranslationZ(view, translationZ);
+        view.setScaleX(scaleX);
+        view.setScaleY(scaleY);
+        view.setRotationX(rotationX);
+        view.setRotationY(rotationY);
+        view.setRotation(rotationZ);
+    }
+
+    private static class Transforms {
+
+        final float mTranslationX;
+        final float mTranslationY;
+        final float mTranslationZ;
+        final float mScaleX;
+        final float mScaleY;
+        final float mRotationX;
+        final float mRotationY;
+        final float mRotationZ;
+
+        Transforms(View view) {
+            mTranslationX = view.getTranslationX();
+            mTranslationY = view.getTranslationY();
+            mTranslationZ = ViewCompat.getTranslationZ(view);
+            mScaleX = view.getScaleX();
+            mScaleY = view.getScaleY();
+            mRotationX = view.getRotationX();
+            mRotationY = view.getRotationY();
+            mRotationZ = view.getRotation();
+        }
+
+        public void restore(View view) {
+            setTransforms(view, mTranslationX, mTranslationY, mTranslationZ, mScaleX, mScaleY,
+                    mRotationX, mRotationY, mRotationZ);
+        }
+
+        @Override
+        public boolean equals(Object that) {
+            if (!(that instanceof Transforms)) {
+                return false;
+            }
+            Transforms thatTransform = (Transforms) that;
+            return thatTransform.mTranslationX == mTranslationX
+                    && thatTransform.mTranslationY == mTranslationY
+                    && thatTransform.mTranslationZ == mTranslationZ
+                    && thatTransform.mScaleX == mScaleX
+                    && thatTransform.mScaleY == mScaleY
+                    && thatTransform.mRotationX == mRotationX
+                    && thatTransform.mRotationY == mRotationY
+                    && thatTransform.mRotationZ == mRotationZ;
+        }
+
+        @Override
+        public int hashCode() {
+            int code = mTranslationX != +0.0f ? Float.floatToIntBits(mTranslationX) : 0;
+            code = 31 * code + (mTranslationY != +0.0f ? Float.floatToIntBits(mTranslationY) : 0);
+            code = 31 * code + (mTranslationZ != +0.0f ? Float.floatToIntBits(mTranslationZ) : 0);
+            code = 31 * code + (mScaleX != +0.0f ? Float.floatToIntBits(mScaleX) : 0);
+            code = 31 * code + (mScaleY != +0.0f ? Float.floatToIntBits(mScaleY) : 0);
+            code = 31 * code + (mRotationX != +0.0f ? Float.floatToIntBits(mRotationX) : 0);
+            code = 31 * code + (mRotationY != +0.0f ? Float.floatToIntBits(mRotationY) : 0);
+            code = 31 * code + (mRotationZ != +0.0f ? Float.floatToIntBits(mRotationZ) : 0);
+            return code;
+        }
+
+    }
+
+    private static class GhostListener extends TransitionListenerAdapter {
+
+        private View mView;
+        private GhostViewImpl mGhostView;
+
+        GhostListener(View view, GhostViewImpl ghostView) {
+            mView = view;
+            mGhostView = ghostView;
+        }
+
+        @Override
+        public void onTransitionEnd(@NonNull Transition transition) {
+            transition.removeListener(this);
+            GhostViewUtils.removeGhost(mView);
+            mView.setTag(R.id.transition_transform, null);
+            mView.setTag(R.id.parent_matrix, null);
+        }
+
+        @Override
+        public void onTransitionPause(@NonNull Transition transition) {
+            mGhostView.setVisibility(View.INVISIBLE);
+        }
+
+        @Override
+        public void onTransitionResume(@NonNull Transition transition) {
+            mGhostView.setVisibility(View.VISIBLE);
+        }
+
+    }
+
+    /**
+     * PathAnimatorMatrix allows the translations and the rest of the matrix to be set
+     * separately. This allows the PathMotion to affect the translations while scale
+     * and rotation are evaluated separately.
+     */
+    private static class PathAnimatorMatrix {
+
+        private final Matrix mMatrix = new Matrix();
+        private final View mView;
+        private final float[] mValues;
+        private float mTranslationX;
+        private float mTranslationY;
+
+        PathAnimatorMatrix(View view, float[] values) {
+            mView = view;
+            mValues = values.clone();
+            mTranslationX = mValues[Matrix.MTRANS_X];
+            mTranslationY = mValues[Matrix.MTRANS_Y];
+            setAnimationMatrix();
+        }
+
+        void setValues(float[] values) {
+            System.arraycopy(values, 0, mValues, 0, values.length);
+            setAnimationMatrix();
+        }
+
+        void setTranslation(PointF translation) {
+            mTranslationX = translation.x;
+            mTranslationY = translation.y;
+            setAnimationMatrix();
+        }
+
+        private void setAnimationMatrix() {
+            mValues[Matrix.MTRANS_X] = mTranslationX;
+            mValues[Matrix.MTRANS_Y] = mTranslationY;
+            mMatrix.setValues(mValues);
+            ViewUtils.setAnimationMatrix(mView, mMatrix);
+        }
+
+        Matrix getMatrix() {
+            return mMatrix;
+        }
+    }
+
+}
diff --git a/androidx/transition/ChangeTransformTest.java b/androidx/transition/ChangeTransformTest.java
new file mode 100644
index 0000000..b13f0b8
--- /dev/null
+++ b/androidx/transition/ChangeTransformTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.filters.MediumTest;
+import android.view.View;
+
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class ChangeTransformTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new ChangeTransform();
+    }
+
+    @Test
+    public void testTranslation() throws Throwable {
+        enterScene(R.layout.scene1);
+
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setTranslationX(500);
+                redSquare.setTranslationY(600);
+            }
+        });
+        waitForStart();
+
+        verify(mListener, never()).onTransitionEnd(any(Transition.class)); // still running
+        // There is no way to validate the intermediate matrix because it uses
+        // hidden properties of the View to execute.
+        waitForEnd();
+        assertEquals(500f, redSquare.getTranslationX(), 0.0f);
+        assertEquals(600f, redSquare.getTranslationY(), 0.0f);
+    }
+
+    @Test
+    public void testRotation() throws Throwable {
+        enterScene(R.layout.scene1);
+
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setRotation(45);
+            }
+        });
+        waitForStart();
+
+        verify(mListener, never()).onTransitionEnd(any(Transition.class)); // still running
+        // There is no way to validate the intermediate matrix because it uses
+        // hidden properties of the View to execute.
+        waitForEnd();
+        assertEquals(45f, redSquare.getRotation(), 0.0f);
+    }
+
+    @Test
+    public void testScale() throws Throwable {
+        enterScene(R.layout.scene1);
+
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setScaleX(2f);
+                redSquare.setScaleY(3f);
+            }
+        });
+        waitForStart();
+
+        verify(mListener, never()).onTransitionEnd(any(Transition.class)); // still running
+        // There is no way to validate the intermediate matrix because it uses
+        // hidden properties of the View to execute.
+        waitForEnd();
+        assertEquals(2f, redSquare.getScaleX(), 0.0f);
+        assertEquals(3f, redSquare.getScaleY(), 0.0f);
+    }
+
+    @Test
+    public void testReparent() throws Throwable {
+        final ChangeTransform changeTransform = (ChangeTransform) mTransition;
+        assertEquals(true, changeTransform.getReparent());
+        enterScene(R.layout.scene5);
+        startTransition(R.layout.scene9);
+        verify(mListener, never()).onTransitionEnd(any(Transition.class)); // still running
+        waitForEnd();
+
+        resetListener();
+        changeTransform.setReparent(false);
+        assertEquals(false, changeTransform.getReparent());
+        startTransition(R.layout.scene5);
+        waitForEnd(); // no transition to run because reparent == false
+    }
+
+}
diff --git a/androidx/transition/CheckCalledRunnable.java b/androidx/transition/CheckCalledRunnable.java
new file mode 100644
index 0000000..f33b72c
--- /dev/null
+++ b/androidx/transition/CheckCalledRunnable.java
@@ -0,0 +1,35 @@
+/*
+ * 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 androidx.transition;
+
+class CheckCalledRunnable implements Runnable {
+
+    private boolean mWasCalled = false;
+
+    @Override
+    public void run() {
+        mWasCalled = true;
+    }
+
+    /**
+     * @return {@code true} if {@link #run()} was called at least once.
+     */
+    boolean wasCalled() {
+        return mWasCalled;
+    }
+
+}
diff --git a/androidx/transition/CircularPropagation.java b/androidx/transition/CircularPropagation.java
new file mode 100644
index 0000000..685d596
--- /dev/null
+++ b/androidx/transition/CircularPropagation.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A propagation that varies with the distance to the epicenter of the Transition
+ * or center of the scene if no epicenter exists. When a View is visible in the
+ * start of the transition, Views farther from the epicenter will transition
+ * sooner than Views closer to the epicenter. When a View is not in the start
+ * of the transition or is not visible at the start of the transition, it will
+ * transition sooner when closer to the epicenter and later when farther from
+ * the epicenter. This is the default TransitionPropagation used with
+ * {@link Explode}.
+ */
+public class CircularPropagation extends VisibilityPropagation {
+
+    private float mPropagationSpeed = 3.0f;
+
+    /**
+     * Sets the speed at which transition propagation happens, relative to the duration of the
+     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered farthest from
+     * the epicenter and View centered at the epicenter will have a difference
+     * in start delay of approximately the duration of the Transition. A speed of 2 means the
+     * start delay difference will be approximately half of the duration of the transition. A
+     * value of 0 is illegal, but negative values will invert the propagation.
+     *
+     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+     *                         of the transition. A speed of 4 means it works 4 times as fast
+     *                         as the duration of the transition. May not be 0.
+     */
+    public void setPropagationSpeed(float propagationSpeed) {
+        if (propagationSpeed == 0) {
+            throw new IllegalArgumentException("propagationSpeed may not be 0");
+        }
+        mPropagationSpeed = propagationSpeed;
+    }
+
+    @Override
+    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null && endValues == null) {
+            return 0;
+        }
+        int directionMultiplier = 1;
+        TransitionValues positionValues;
+        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+            positionValues = startValues;
+            directionMultiplier = -1;
+        } else {
+            positionValues = endValues;
+        }
+
+        int viewCenterX = getViewX(positionValues);
+        int viewCenterY = getViewY(positionValues);
+
+        Rect epicenter = transition.getEpicenter();
+        int epicenterX;
+        int epicenterY;
+        if (epicenter != null) {
+            epicenterX = epicenter.centerX();
+            epicenterY = epicenter.centerY();
+        } else {
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            epicenterX = Math.round(loc[0] + (sceneRoot.getWidth() / 2)
+                    + sceneRoot.getTranslationX());
+            epicenterY = Math.round(loc[1] + (sceneRoot.getHeight() / 2)
+                    + sceneRoot.getTranslationY());
+        }
+        float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY);
+        float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+        float distanceFraction = distance / maxDistance;
+
+        long duration = transition.getDuration();
+        if (duration < 0) {
+            duration = 300;
+        }
+
+        return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
+    }
+
+    private static float distance(float x1, float y1, float x2, float y2) {
+        float x = x2 - x1;
+        float y = y2 - y1;
+        return (float) Math.sqrt((x * x) + (y * y));
+    }
+
+}
diff --git a/androidx/transition/Explode.java b/androidx/transition/Explode.java
new file mode 100644
index 0000000..1dc6b3a
--- /dev/null
+++ b/androidx/transition/Explode.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import androidx.annotation.NonNull;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ * <p>Views move away from the focal View or the center of the Scene if
+ * no epicenter was provided.</p>
+ */
+public class Explode extends Visibility {
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+    private static final String PROPNAME_SCREEN_BOUNDS = "android:explode:screenBounds";
+
+    private int[] mTempLoc = new int[2];
+
+    public Explode() {
+        setPropagation(new CircularPropagation());
+    }
+
+    public Explode(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setPropagation(new CircularPropagation());
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        view.getLocationOnScreen(mTempLoc);
+        int left = mTempLoc[0];
+        int top = mTempLoc[1];
+        int right = left + view.getWidth();
+        int bottom = top + view.getHeight();
+        transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+        Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        float endX = view.getTranslationX();
+        float endY = view.getTranslationY();
+        calculateOut(sceneRoot, bounds, mTempLoc);
+        float startX = endX + mTempLoc[0];
+        float startY = endY + mTempLoc[1];
+
+        return TranslationAnimationCreator.createAnimation(view, endValues, bounds.left, bounds.top,
+                startX, startY, endX, endY, sDecelerate);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null) {
+            return null;
+        }
+        Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        int viewPosX = bounds.left;
+        int viewPosY = bounds.top;
+        float startX = view.getTranslationX();
+        float startY = view.getTranslationY();
+        float endX = startX;
+        float endY = startY;
+        int[] interruptedPosition = (int[]) startValues.view.getTag(R.id.transition_position);
+        if (interruptedPosition != null) {
+            // We want to have the end position relative to the interrupted position, not
+            // the position it was supposed to start at.
+            endX += interruptedPosition[0] - bounds.left;
+            endY += interruptedPosition[1] - bounds.top;
+            bounds.offsetTo(interruptedPosition[0], interruptedPosition[1]);
+        }
+        calculateOut(sceneRoot, bounds, mTempLoc);
+        endX += mTempLoc[0];
+        endY += mTempLoc[1];
+
+        return TranslationAnimationCreator.createAnimation(view, startValues,
+                viewPosX, viewPosY, startX, startY, endX, endY, sAccelerate);
+    }
+
+    private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
+        sceneRoot.getLocationOnScreen(mTempLoc);
+        int sceneRootX = mTempLoc[0];
+        int sceneRootY = mTempLoc[1];
+        int focalX;
+        int focalY;
+
+        Rect epicenter = getEpicenter();
+        if (epicenter == null) {
+            focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+                    + Math.round(sceneRoot.getTranslationX());
+            focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+                    + Math.round(sceneRoot.getTranslationY());
+        } else {
+            focalX = epicenter.centerX();
+            focalY = epicenter.centerY();
+        }
+
+        int centerX = bounds.centerX();
+        int centerY = bounds.centerY();
+        float xVector = centerX - focalX;
+        float yVector = centerY - focalY;
+
+        if (xVector == 0 && yVector == 0) {
+            // Random direction when View is centered on focal View.
+            xVector = (float) (Math.random() * 2) - 1;
+            yVector = (float) (Math.random() * 2) - 1;
+        }
+        float vectorSize = calculateDistance(xVector, yVector);
+        xVector /= vectorSize;
+        yVector /= vectorSize;
+
+        float maxDistance =
+                calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
+
+        outVector[0] = Math.round(maxDistance * xVector);
+        outVector[1] = Math.round(maxDistance * yVector);
+    }
+
+    private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
+        int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
+        int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
+        return calculateDistance(maxX, maxY);
+    }
+
+    private static float calculateDistance(float x, float y) {
+        return (float) Math.sqrt((x * x) + (y * y));
+    }
+
+}
diff --git a/androidx/transition/ExplodeTest.java b/androidx/transition/ExplodeTest.java
new file mode 100644
index 0000000..330d0bc
--- /dev/null
+++ b/androidx/transition/ExplodeTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class ExplodeTest extends BaseTransitionTest {
+
+    @Override
+    Transition createTransition() {
+        return new Explode();
+    }
+
+    @Test
+    public void testExplode() throws Throwable {
+        enterScene(R.layout.scene10);
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+        final View blueSquare = rule.getActivity().findViewById(R.id.blueSquare);
+        final View yellowSquare = rule.getActivity().findViewById(R.id.yellowSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setVisibility(View.INVISIBLE);
+                greenSquare.setVisibility(View.INVISIBLE);
+                blueSquare.setVisibility(View.INVISIBLE);
+                yellowSquare.setVisibility(View.INVISIBLE);
+            }
+        });
+        waitForStart();
+        verify(mListener, never()).onTransitionEnd(any(Transition.class));
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+        float redStartX = redSquare.getTranslationX();
+        float redStartY = redSquare.getTranslationY();
+
+        SystemClock.sleep(100);
+        verifyTranslation(redSquare, true, true);
+        verifyTranslation(greenSquare, false, true);
+        verifyTranslation(blueSquare, false, false);
+        verifyTranslation(yellowSquare, true, false);
+        assertThat(redStartX, is(greaterThan(redSquare.getTranslationX()))); // moving left
+        assertThat(redStartY, is(greaterThan(redSquare.getTranslationY()))); // moving up
+        waitForEnd();
+
+        verifyNoTranslation(redSquare);
+        verifyNoTranslation(greenSquare);
+        verifyNoTranslation(blueSquare);
+        verifyNoTranslation(yellowSquare);
+        assertEquals(View.INVISIBLE, redSquare.getVisibility());
+        assertEquals(View.INVISIBLE, greenSquare.getVisibility());
+        assertEquals(View.INVISIBLE, blueSquare.getVisibility());
+        assertEquals(View.INVISIBLE, yellowSquare.getVisibility());
+    }
+
+    @Test
+    public void testImplode() throws Throwable {
+        enterScene(R.layout.scene10);
+        final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+        final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+        final View blueSquare = rule.getActivity().findViewById(R.id.blueSquare);
+        final View yellowSquare = rule.getActivity().findViewById(R.id.yellowSquare);
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                redSquare.setVisibility(View.INVISIBLE);
+                greenSquare.setVisibility(View.INVISIBLE);
+                blueSquare.setVisibility(View.INVISIBLE);
+                yellowSquare.setVisibility(View.INVISIBLE);
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(mRoot, mTransition);
+                redSquare.setVisibility(View.VISIBLE);
+                greenSquare.setVisibility(View.VISIBLE);
+                blueSquare.setVisibility(View.VISIBLE);
+                yellowSquare.setVisibility(View.VISIBLE);
+            }
+        });
+        waitForStart();
+
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+        float redStartX = redSquare.getTranslationX();
+        float redStartY = redSquare.getTranslationY();
+
+        SystemClock.sleep(100);
+        verifyTranslation(redSquare, true, true);
+        verifyTranslation(greenSquare, false, true);
+        verifyTranslation(blueSquare, false, false);
+        verifyTranslation(yellowSquare, true, false);
+        assertThat(redStartX, is(lessThan(redSquare.getTranslationX()))); // moving right
+        assertThat(redStartY, is(lessThan(redSquare.getTranslationY()))); // moving down
+        waitForEnd();
+
+        verifyNoTranslation(redSquare);
+        verifyNoTranslation(greenSquare);
+        verifyNoTranslation(blueSquare);
+        verifyNoTranslation(yellowSquare);
+        assertEquals(View.VISIBLE, redSquare.getVisibility());
+        assertEquals(View.VISIBLE, greenSquare.getVisibility());
+        assertEquals(View.VISIBLE, blueSquare.getVisibility());
+        assertEquals(View.VISIBLE, yellowSquare.getVisibility());
+    }
+
+    private void verifyTranslation(View view, boolean goLeft, boolean goUp) {
+        float translationX = view.getTranslationX();
+        float translationY = view.getTranslationY();
+
+        if (goLeft) {
+            assertThat(translationX, is(lessThan(0.f)));
+        } else {
+            assertThat(translationX, is(greaterThan(0.f)));
+        }
+
+        if (goUp) {
+            assertThat(translationY, is(lessThan(0.f)));
+        } else {
+            assertThat(translationY, is(greaterThan(0.f)));
+        }
+    }
+
+    private void verifyNoTranslation(View view) {
+        assertEquals(0f, view.getTranslationX(), 0.0f);
+        assertEquals(0f, view.getTranslationY(), 0.0f);
+    }
+
+}
diff --git a/androidx/transition/Fade.java b/androidx/transition/Fade.java
new file mode 100644
index 0000000..ab484f5
--- /dev/null
+++ b/androidx/transition/Fade.java
@@ -0,0 +1,206 @@
+/*
+ * 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.view.ViewCompat;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and fades views in or out when they become visible
+ * or non-visible. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy.
+ *
+ * <p>The ability of this transition to fade out a particular view, and the
+ * way that that fading operation takes place, is based on
+ * the situation of the view in the view hierarchy. For example, if a view was
+ * simply removed from its parent, then the view will be added into a {@link
+ * android.view.ViewGroupOverlay} while fading. If a visible view is
+ * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the
+ * visibility will be changed to {@link View#VISIBLE} for the duration of
+ * the animation. However, if a view is in a hierarchy which is also altering
+ * its visibility, the situation can be more complicated. In general, if a
+ * view that is no longer in the hierarchy in the end scene still has a
+ * parent (so its parent hierarchy was removed, but it was not removed from
+ * its parent), then it will be left alone to avoid side-effects from
+ * improperly removing it from its parent. The only exception to this is if
+ * the previous {@link Scene} was
+ * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context)
+ * created from a layout resource file}, then it is considered safe to un-parent
+ * the starting scene view in order to fade it out.</p>
+ *
+ * <p>A Fade transition can be described in a resource file by using the
+ * tag <code>fade</code>, along with the standard
+ * attributes of {@code Fade} and {@link Transition}.</p>
+ */
+public class Fade extends Visibility {
+
+    private static final String PROPNAME_TRANSITION_ALPHA = "android:fade:transitionAlpha";
+
+    private static final String LOG_TAG = "Fade";
+
+    /**
+     * Fading mode used in {@link #Fade(int)} to make the transition
+     * operate on targets that are appearing. Maybe be combined with
+     * {@link #OUT} to fade both in and out.
+     */
+    public static final int IN = Visibility.MODE_IN;
+
+    /**
+     * Fading mode used in {@link #Fade(int)} to make the transition
+     * operate on targets that are disappearing. Maybe be combined with
+     * {@link #IN} to fade both in and out.
+     */
+    public static final int OUT = Visibility.MODE_OUT;
+
+    /**
+     * Constructs a Fade transition that will fade targets in
+     * and/or out, according to the value of fadingMode.
+     *
+     * @param fadingMode The behavior of this transition, a combination of
+     *                   {@link #IN} and {@link #OUT}.
+     */
+    public Fade(int fadingMode) {
+        setMode(fadingMode);
+    }
+
+    /**
+     * Constructs a Fade transition that will fade targets in and out.
+     */
+    public Fade() {
+    }
+
+    public Fade(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.FADE);
+        @Mode
+        int fadingMode = TypedArrayUtils.getNamedInt(a, (XmlResourceParser) attrs, "fadingMode",
+                Styleable.Fade.FADING_MODE, getMode());
+        setMode(fadingMode);
+        a.recycle();
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        transitionValues.values.put(PROPNAME_TRANSITION_ALPHA,
+                ViewUtils.getTransitionAlpha(transitionValues.view));
+    }
+
+    /**
+     * Utility method to handle creating and running the Animator.
+     */
+    private Animator createAnimation(final View view, float startAlpha, float endAlpha) {
+        if (startAlpha == endAlpha) {
+            return null;
+        }
+        ViewUtils.setTransitionAlpha(view, startAlpha);
+        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, ViewUtils.TRANSITION_ALPHA,
+                endAlpha);
+        if (DBG) {
+            Log.d(LOG_TAG, "Created animator " + anim);
+        }
+        FadeAnimatorListener listener = new FadeAnimatorListener(view);
+        anim.addListener(listener);
+        addListener(new TransitionListenerAdapter() {
+            @Override
+            public void onTransitionEnd(@NonNull Transition transition) {
+                ViewUtils.setTransitionAlpha(view, 1);
+                ViewUtils.clearNonTransitionAlpha(view);
+                transition.removeListener(this);
+            }
+        });
+        return anim;
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues,
+            TransitionValues endValues) {
+        if (DBG) {
+            View startView = (startValues != null) ? startValues.view : null;
+            Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = "
+                    + startView + ", " + view);
+        }
+        float startAlpha = getStartAlpha(startValues, 0);
+        if (startAlpha == 1) {
+            startAlpha = 0;
+        }
+        return createAnimation(view, startAlpha, 1);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
+            TransitionValues endValues) {
+        ViewUtils.saveNonTransitionAlpha(view);
+        float startAlpha = getStartAlpha(startValues, 1);
+        return createAnimation(view, startAlpha, 0);
+    }
+
+    private static float getStartAlpha(TransitionValues startValues, float fallbackValue) {
+        float startAlpha = fallbackValue;
+        if (startValues != null) {
+            Float startAlphaFloat = (Float) startValues.values.get(PROPNAME_TRANSITION_ALPHA);
+            if (startAlphaFloat != null) {
+                startAlpha = startAlphaFloat;
+            }
+        }
+        return startAlpha;
+    }
+
+    private static class FadeAnimatorListener extends AnimatorListenerAdapter {
+
+        private final View mView;
+        private boolean mLayerTypeChanged = false;
+
+        FadeAnimatorListener(View view) {
+            mView = view;
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+            if (ViewCompat.hasOverlappingRendering(mView)
+                    && mView.getLayerType() == View.LAYER_TYPE_NONE) {
+                mLayerTypeChanged = true;
+                mView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            ViewUtils.setTransitionAlpha(mView, 1);
+            if (mLayerTypeChanged) {
+                mView.setLayerType(View.LAYER_TYPE_NONE, null);
+            }
+        }
+
+    }
+
+}
diff --git a/androidx/transition/FadeTest.java b/androidx/transition/FadeTest.java
new file mode 100644
index 0000000..af507be
--- /dev/null
+++ b/androidx/transition/FadeTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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 androidx.transition;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@MediumTest
+public class FadeTest extends BaseTest {
+
+    private View mView;
+    private ViewGroup mRoot;
+
+    @UiThreadTest
+    @Before
+    public void setUp() {
+        mRoot = rule.getActivity().getRoot();
+        mView = new View(rule.getActivity());
+        mRoot.addView(mView, new ViewGroup.LayoutParams(100, 100));
+    }
+
+    @Test
+    public void testMode() {
+        assertThat(Fade.IN, is(Visibility.MODE_IN));
+        assertThat(Fade.OUT, is(Visibility.MODE_OUT));
+        final Fade fade = new Fade();
+        assertThat(fade.getMode(), is(Visibility.MODE_IN | Visibility.MODE_OUT));
+        fade.setMode(Visibility.MODE_IN);
+        assertThat(fade.getMode(), is(Visibility.MODE_IN));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testDisappear() {
+        final Fade fade = new Fade();
+        final TransitionValues startValues = new TransitionValues();
+        startValues.view = mView;
+        fade.captureStartValues(startValues);
+        mView.setVisibility(View.INVISIBLE);
+        final TransitionValues endValues = new TransitionValues();
+        endValues.view = mView;
+        fade.captureEndValues(endValues);
+        Animator animator = fade.createAnimator(mRoot, startValues, endValues);
+        assertThat(animator, is(notNullValue()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testAppear() {
+        mView.setVisibility(View.INVISIBLE);
+        final Fade fade = new Fade();
+        final TransitionValues startValues = new TransitionValues();
+        startValues.view = mView;
+        fade.captureStartValues(startValues);
+        mView.setVisibility(View.VISIBLE);
+        final TransitionValues endValues = new TransitionValues();
+        endValues.view = mView;
+        fade.captureEndValues(endValues);
+        Animator animator = fade.createAnimator(mRoot, startValues, endValues);
+        assertThat(animator, is(notNullValue()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testNoChange() {
+        final Fade fade = new Fade();
+        final TransitionValues startValues = new TransitionValues();
+        startValues.view = mView;
+        fade.captureStartValues(startValues);
+        final TransitionValues endValues = new TransitionValues();
+        endValues.view = mView;
+        fade.captureEndValues(endValues);
+        Animator animator = fade.createAnimator(mRoot, startValues, endValues);
+        // No visibility change; no animation should happen
+        assertThat(animator, is(nullValue()));
+    }
+
+    @Test
+    public void testFadeOutThenIn() throws Throwable {
+        // Fade out
+        final Runnable interrupt = mock(Runnable.class);
+        float[] valuesOut = new float[2];
+        final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, interrupt,
+                valuesOut);
+        final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
+        fadeOut.addListener(listenerOut);
+        changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
+        verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
+
+        // The view is in the middle of fading out
+        verify(interrupt, timeout(3000)).run();
+
+        // Fade in
+        float[] valuesIn = new float[2];
+        final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, null, valuesIn);
+        final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
+        fadeIn.addListener(listenerIn);
+        changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
+        verify(listenerOut, timeout(3000)).onTransitionPause(any(Transition.class));
+        verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
+        assertThat(valuesOut[1], allOf(greaterThan(0f), lessThan(1f)));
+        if (Build.VERSION.SDK_INT >= 19 && fadeOut.mInitialAlpha >= 0) {
+            // These won't match on API levels 18 and below due to lack of Animator pause.
+            assertEquals(valuesOut[1], valuesIn[0], 0.01f);
+        }
+
+        verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class));
+        assertThat(mView.getVisibility(), is(View.VISIBLE));
+        assertEquals(valuesIn[1], 1.f, 0.01f);
+    }
+
+    @Test
+    public void testFadeInThenOut() throws Throwable {
+        changeVisibility(null, mRoot, mView, View.INVISIBLE);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+        // Fade in
+        final Runnable interrupt = mock(Runnable.class);
+        float[] valuesIn = new float[2];
+        final InterruptibleFade fadeIn = new InterruptibleFade(Fade.MODE_IN, interrupt, valuesIn);
+        final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
+        fadeIn.addListener(listenerIn);
+        changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
+        verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
+
+        // The view is in the middle of fading in
+        verify(interrupt, timeout(3000)).run();
+
+        // Fade out
+        float[] valuesOut = new float[2];
+        final InterruptibleFade fadeOut = new InterruptibleFade(Fade.MODE_OUT, null, valuesOut);
+        final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
+        fadeOut.addListener(listenerOut);
+        changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
+        verify(listenerIn, timeout(3000)).onTransitionPause(any(Transition.class));
+        verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
+        assertThat(valuesIn[1], allOf(greaterThan(0f), lessThan(1f)));
+        if (Build.VERSION.SDK_INT >= 19 && fadeIn.mInitialAlpha >= 0) {
+            // These won't match on API levels 18 and below due to lack of Animator pause.
+            assertEquals(valuesIn[1], valuesOut[0], 0.01f);
+        }
+
+        verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class));
+        assertThat(mView.getVisibility(), is(View.INVISIBLE));
+    }
+
+    @Test
+    public void testFadeWithAlpha() throws Throwable {
+        // Set the view alpha to 0.5
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mView.setAlpha(0.5f);
+            }
+        });
+        // Fade out
+        final Fade fadeOut = new Fade(Fade.OUT);
+        final Transition.TransitionListener listenerOut = mock(Transition.TransitionListener.class);
+        fadeOut.addListener(listenerOut);
+        changeVisibility(fadeOut, mRoot, mView, View.INVISIBLE);
+        verify(listenerOut, timeout(3000)).onTransitionStart(any(Transition.class));
+        verify(listenerOut, timeout(3000)).onTransitionEnd(any(Transition.class));
+        // Fade in
+        final Fade fadeIn = new Fade(Fade.IN);
+        final Transition.TransitionListener listenerIn = mock(Transition.TransitionListener.class);
+        fadeIn.addListener(listenerIn);
+        changeVisibility(fadeIn, mRoot, mView, View.VISIBLE);
+        verify(listenerIn, timeout(3000)).onTransitionStart(any(Transition.class));
+        verify(listenerIn, timeout(3000)).onTransitionEnd(any(Transition.class));
+        // Confirm that the view still has the original alpha value
+        assertThat(mView.getVisibility(), is(View.VISIBLE));
+        assertEquals(0.5f, mView.getAlpha(), 0.01f);
+    }
+
+    private void changeVisibility(final Fade fade, final ViewGroup container, final View target,
+            final int visibility) throws Throwable {
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                if (fade != null) {
+                    TransitionManager.beginDelayedTransition(container, fade);
+                }
+                target.setVisibility(visibility);
+            }
+        });
+    }
+
+    /**
+     * A special version of {@link Fade} that runs a specified {@link Runnable} soon after the
+     * target starts fading in or out.
+     */
+    private static class InterruptibleFade extends Fade {
+
+        static final float ALPHA_THRESHOLD = 0.2f;
+
+        float mInitialAlpha = -1;
+        Runnable mMiddle;
+        final float[] mAlphaValues;
+
+        InterruptibleFade(int mode, Runnable middle, float[] alphaValues) {
+            super(mode);
+            mMiddle = middle;
+            mAlphaValues = alphaValues;
+        }
+
+        @Nullable
+        @Override
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                @Nullable final TransitionValues startValues,
+                @Nullable final TransitionValues endValues) {
+            final Animator animator = super.createAnimator(sceneRoot, startValues, endValues);
+            if (animator instanceof ObjectAnimator) {
+                ((ObjectAnimator) animator).addUpdateListener(
+                        new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                final float alpha = (float) animation.getAnimatedValue();
+                                mAlphaValues[1] = alpha;
+                                if (mInitialAlpha < 0) {
+                                    mInitialAlpha = alpha;
+                                    mAlphaValues[0] = mInitialAlpha;
+                                } else if (Math.abs(alpha - mInitialAlpha) > ALPHA_THRESHOLD) {
+                                    if (mMiddle != null) {
+                                        mMiddle.run();
+                                        mMiddle = null;
+                                    }
+                                }
+                            }
+                        });
+            }
+            return animator;
+        }
+
+    }
+
+}
diff --git a/androidx/transition/FloatArrayEvaluator.java b/androidx/transition/FloatArrayEvaluator.java
new file mode 100644
index 0000000..9947921
--- /dev/null
+++ b/androidx/transition/FloatArrayEvaluator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.TypeEvaluator;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>float[]</code> values.
+ * Each index into the array is treated as a separate value to interpolate. For example,
+ * evaluating <code>{100, 200}</code> and <code>{300, 400}</code> will interpolate the value at
+ * the first index between 100 and 300 and the value at the second index value between 200 and 400.
+ */
+class FloatArrayEvaluator implements TypeEvaluator<float[]> {
+
+    private float[] mArray;
+
+    /**
+     * Create a FloatArrayEvaluator that reuses <code>reuseArray</code> for every evaluate() call.
+     * Caution must be taken to ensure that the value returned from
+     * {@link android.animation.ValueAnimator#getAnimatedValue()} is not cached, modified, or
+     * used across threads. The value will be modified on each <code>evaluate()</code> call.
+     *
+     * @param reuseArray The array to modify and return from <code>evaluate</code>.
+     */
+    FloatArrayEvaluator(float[] reuseArray) {
+        mArray = reuseArray;
+    }
+
+    /**
+     * Interpolates the value at each index by the fraction. If
+     * {@link #FloatArrayEvaluator(float[])} was used to construct this object,
+     * <code>reuseArray</code> will be returned, otherwise a new <code>float[]</code>
+     * will be returned.
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start value.
+     * @param endValue   The end value.
+     * @return A <code>float[]</code> where each element is an interpolation between
+     * the same index in startValue and endValue.
+     */
+    @Override
+    public float[] evaluate(float fraction, float[] startValue, float[] endValue) {
+        float[] array = mArray;
+        if (array == null) {
+            array = new float[startValue.length];
+        }
+
+        for (int i = 0; i < array.length; i++) {
+            float start = startValue[i];
+            float end = endValue[i];
+            array[i] = start + (fraction * (end - start));
+        }
+        return array;
+    }
+
+}
diff --git a/androidx/transition/FragmentTransitionSupport.java b/androidx/transition/FragmentTransitionSupport.java
new file mode 100644
index 0000000..9e72ff3
--- /dev/null
+++ b/androidx/transition/FragmentTransitionSupport.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.fragment.app.FragmentTransitionImpl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @hide
+ */
+// This is instantiated in androidx.fragment.app.FragmentTransition
+@SuppressWarnings("unused")
+@RestrictTo(LIBRARY_GROUP)
+public class FragmentTransitionSupport extends FragmentTransitionImpl {
+
+    @Override
+    public boolean canHandle(Object transition) {
+        return transition instanceof Transition;
+    }
+
+    @Override
+    public Object cloneTransition(Object transition) {
+        Transition copy = null;
+        if (transition != null) {
+            copy = ((Transition) transition).clone();
+        }
+        return copy;
+    }
+
+    @Override
+    public Object wrapTransitionInSet(Object transition) {
+        if (transition == null) {
+            return null;
+        }
+        TransitionSet transitionSet = new TransitionSet();
+        transitionSet.addTransition((Transition) transition);
+        return transitionSet;
+    }
+
+    @Override
+    public void setSharedElementTargets(Object transitionObj,
+            View nonExistentView, ArrayList<View> sharedViews) {
+        TransitionSet transition = (TransitionSet) transitionObj;
+        final List<View> views = transition.getTargets();
+        views.clear();
+        final int count = sharedViews.size();
+        for (int i = 0; i < count; i++) {
+            final View view = sharedViews.get(i);
+            bfsAddViewChildren(views, view);
+        }
+        views.add(nonExistentView);
+        sharedViews.add(nonExistentView);
+        addTargets(transition, sharedViews);
+    }
+
+    @Override
+    public void setEpicenter(Object transitionObj, View view) {
+        if (view != null) {
+            Transition transition = (Transition) transitionObj;
+            final Rect epicenter = new Rect();
+            getBoundsOnScreen(view, epicenter);
+
+            transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+                @Override
+                public Rect onGetEpicenter(@NonNull Transition transition) {
+                    return epicenter;
+                }
+            });
+        }
+    }
+
+    @Override
+    public void addTargets(Object transitionObj, ArrayList<View> views) {
+        Transition transition = (Transition) transitionObj;
+        if (transition == null) {
+            return;
+        }
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                addTargets(child, views);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (isNullOrEmpty(targets)) {
+                // We can just add the target views
+                int numViews = views.size();
+                for (int i = 0; i < numViews; i++) {
+                    transition.addTarget(views.get(i));
+                }
+            }
+        }
+    }
+
+    private static boolean hasSimpleTarget(Transition transition) {
+        return !isNullOrEmpty(transition.getTargetIds())
+                || !isNullOrEmpty(transition.getTargetNames())
+                || !isNullOrEmpty(transition.getTargetTypes());
+    }
+
+    @Override
+    public Object mergeTransitionsTogether(Object transition1, Object transition2,
+            Object transition3) {
+        TransitionSet transitionSet = new TransitionSet();
+        if (transition1 != null) {
+            transitionSet.addTransition((Transition) transition1);
+        }
+        if (transition2 != null) {
+            transitionSet.addTransition((Transition) transition2);
+        }
+        if (transition3 != null) {
+            transitionSet.addTransition((Transition) transition3);
+        }
+        return transitionSet;
+    }
+
+    @Override
+    public void scheduleHideFragmentView(Object exitTransitionObj, final View fragmentView,
+            final ArrayList<View> exitingViews) {
+        Transition exitTransition = (Transition) exitTransitionObj;
+        exitTransition.addListener(new Transition.TransitionListener() {
+            @Override
+            public void onTransitionStart(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionEnd(@NonNull Transition transition) {
+                transition.removeListener(this);
+                fragmentView.setVisibility(View.GONE);
+                final int numViews = exitingViews.size();
+                for (int i = 0; i < numViews; i++) {
+                    exitingViews.get(i).setVisibility(View.VISIBLE);
+                }
+            }
+
+            @Override
+            public void onTransitionCancel(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionPause(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionResume(@NonNull Transition transition) {
+            }
+        });
+    }
+
+    @Override
+    public Object mergeTransitionsInSequence(Object exitTransitionObj,
+            Object enterTransitionObj, Object sharedElementTransitionObj) {
+        // First do exit, then enter, but allow shared element transition to happen
+        // during both.
+        Transition staggered = null;
+        final Transition exitTransition = (Transition) exitTransitionObj;
+        final Transition enterTransition = (Transition) enterTransitionObj;
+        final Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
+        if (exitTransition != null && enterTransition != null) {
+            staggered = new TransitionSet()
+                    .addTransition(exitTransition)
+                    .addTransition(enterTransition)
+                    .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
+        } else if (exitTransition != null) {
+            staggered = exitTransition;
+        } else if (enterTransition != null) {
+            staggered = enterTransition;
+        }
+        if (sharedElementTransition != null) {
+            TransitionSet together = new TransitionSet();
+            if (staggered != null) {
+                together.addTransition(staggered);
+            }
+            together.addTransition(sharedElementTransition);
+            return together;
+        } else {
+            return staggered;
+        }
+    }
+
+    @Override
+    public void beginDelayedTransition(ViewGroup sceneRoot, Object transition) {
+        TransitionManager.beginDelayedTransition(sceneRoot, (Transition) transition);
+    }
+
+    @Override
+    public void scheduleRemoveTargets(final Object overallTransitionObj,
+            final Object enterTransition, final ArrayList<View> enteringViews,
+            final Object exitTransition, final ArrayList<View> exitingViews,
+            final Object sharedElementTransition, final ArrayList<View> sharedElementsIn) {
+        final Transition overallTransition = (Transition) overallTransitionObj;
+        overallTransition.addListener(new Transition.TransitionListener() {
+            @Override
+            public void onTransitionStart(@NonNull Transition transition) {
+                if (enterTransition != null) {
+                    replaceTargets(enterTransition, enteringViews, null);
+                }
+                if (exitTransition != null) {
+                    replaceTargets(exitTransition, exitingViews, null);
+                }
+                if (sharedElementTransition != null) {
+                    replaceTargets(sharedElementTransition, sharedElementsIn, null);
+                }
+            }
+
+            @Override
+            public void onTransitionEnd(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionCancel(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionPause(@NonNull Transition transition) {
+            }
+
+            @Override
+            public void onTransitionResume(@NonNull Transition transition) {
+            }
+        });
+    }
+
+    @Override
+    public void swapSharedElementTargets(Object sharedElementTransitionObj,
+            ArrayList<View> sharedElementsOut, ArrayList<View> sharedElementsIn) {
+        TransitionSet sharedElementTransition = (TransitionSet) sharedElementTransitionObj;
+        if (sharedElementTransition != null) {
+            sharedElementTransition.getTargets().clear();
+            sharedElementTransition.getTargets().addAll(sharedElementsIn);
+            replaceTargets(sharedElementTransition, sharedElementsOut, sharedElementsIn);
+        }
+    }
+
+    @Override
+    public void replaceTargets(Object transitionObj, ArrayList<View> oldTargets,
+            ArrayList<View> newTargets) {
+        Transition transition = (Transition) transitionObj;
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            int numTransitions = set.getTransitionCount();
+            for (int i = 0; i < numTransitions; i++) {
+                Transition child = set.getTransitionAt(i);
+                replaceTargets(child, oldTargets, newTargets);
+            }
+        } else if (!hasSimpleTarget(transition)) {
+            List<View> targets = transition.getTargets();
+            if (targets.size() == oldTargets.size()
+                    && targets.containsAll(oldTargets)) {
+                // We have an exact match. We must have added these earlier in addTargets
+                final int targetCount = newTargets == null ? 0 : newTargets.size();
+                for (int i = 0; i < targetCount; i++) {
+                    transition.addTarget(newTargets.get(i));
+                }
+                for (int i = oldTargets.size() - 1; i >= 0; i--) {
+                    transition.removeTarget(oldTargets.get(i));
+                }
+            }
+        }
+    }
+
+    @Override
+    public void addTarget(Object transitionObj, View view) {
+        if (transitionObj != null) {
+            Transition transition = (Transition) transitionObj;
+            transition.addTarget(view);
+        }
+    }
+
+    @Override
+    public void removeTarget(Object transitionObj, View view) {
+        if (transitionObj != null) {
+            Transition transition = (Transition) transitionObj;
+            transition.removeTarget(view);
+        }
+    }
+
+    @Override
+    public void setEpicenter(Object transitionObj, final Rect epicenter) {
+        if (transitionObj != null) {
+            Transition transition = (Transition) transitionObj;
+            transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+                @Override
+                public Rect onGetEpicenter(@NonNull Transition transition) {
+                    if (epicenter == null || epicenter.isEmpty()) {
+                        return null;
+                    }
+                    return epicenter;
+                }
+            });
+        }
+    }
+
+}
diff --git a/androidx/transition/FragmentTransitionTest.java b/androidx/transition/FragmentTransitionTest.java
new file mode 100644
index 0000000..4ad47aa
--- /dev/null
+++ b/androidx/transition/FragmentTransitionTest.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.os.Bundle;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.MediumTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.Nullable;
+import androidx.collection.SparseArrayCompat;
+import androidx.core.util.Pair;
+import androidx.core.view.ViewCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.transition.test.R;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+
+@MediumTest
+@RunWith(Parameterized.class)
+public class FragmentTransitionTest extends BaseTest {
+
+    @Parameterized.Parameters
+    public static Object[] data() {
+        return new Boolean[]{
+                false, true
+        };
+    }
+
+    private final boolean mReorderingAllowed;
+
+    public FragmentTransitionTest(boolean reorderingAllowed) {
+        mReorderingAllowed = reorderingAllowed;
+    }
+
+    @Test
+    public void preconditions() {
+        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
+        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
+        showFragment(fragment1, false, null);
+        assertNull(fragment1.mRed);
+        assertNotNull(fragment1.mGreen);
+        assertNotNull(fragment1.mBlue);
+        showFragment(fragment2, true, new Pair<>(fragment1.mGreen, "green"));
+        assertNotNull(fragment2.mRed);
+        assertNotNull(fragment2.mGreen);
+        assertNotNull(fragment2.mBlue);
+    }
+
+    @Test
+    public void nonSharedTransition() {
+        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
+        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
+        showFragment(fragment1, false, null);
+        showFragment(fragment2, true, null);
+        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_EXIT))
+                .onTransitionStart(any(Transition.class));
+        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_EXIT), timeout(3000))
+                .onTransitionEnd(any(Transition.class));
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_ENTER))
+                .onTransitionStart(any(Transition.class));
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_ENTER), timeout(3000))
+                .onTransitionEnd(any(Transition.class));
+        popBackStack();
+        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_REENTER))
+                .onTransitionStart(any(Transition.class));
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_RETURN))
+                .onTransitionStart(any(Transition.class));
+    }
+
+    @Test
+    public void sharedTransition() {
+        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
+        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
+        showFragment(fragment1, false, null);
+        showFragment(fragment2, true, new Pair<>(fragment1.mGreen, "green"));
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_ENTER))
+                .onTransitionStart(any(Transition.class));
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_ENTER), timeout(3000))
+                .onTransitionEnd(any(Transition.class));
+        popBackStack();
+        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_RETURN))
+                .onTransitionStart(any(Transition.class));
+    }
+
+    private void showFragment(final Fragment fragment, final boolean addToBackStack,
+            final Pair<View, String> sharedElement) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+                transaction.replace(R.id.root, fragment);
+                transaction.setReorderingAllowed(mReorderingAllowed);
+                if (sharedElement != null) {
+                    transaction.addSharedElement(sharedElement.first, sharedElement.second);
+                }
+                if (addToBackStack) {
+                    transaction.addToBackStack(null);
+                    transaction.commit();
+                    getFragmentManager().executePendingTransactions();
+                } else {
+                    transaction.commitNow();
+                }
+            }
+        });
+        instrumentation.waitForIdleSync();
+    }
+
+    private void popBackStack() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                getFragmentManager().popBackStackImmediate();
+            }
+        });
+        instrumentation.waitForIdleSync();
+    }
+
+    private FragmentManager getFragmentManager() {
+        return rule.getActivity().getSupportFragmentManager();
+    }
+
+    /**
+     * A {@link Fragment} with all kinds of {@link Transition} with tracking listeners.
+     */
+    public static class TransitionFragment extends Fragment {
+
+        static final int TRANSITION_ENTER = 1;
+        static final int TRANSITION_EXIT = 2;
+        static final int TRANSITION_REENTER = 3;
+        static final int TRANSITION_RETURN = 4;
+        static final int TRANSITION_SHARED_ENTER = 5;
+        static final int TRANSITION_SHARED_RETURN = 6;
+
+        private static final String ARG_LAYOUT_ID = "layout_id";
+
+        View mRed;
+        View mGreen;
+        View mBlue;
+
+        SparseArrayCompat<Transition.TransitionListener> mListeners = new SparseArrayCompat<>();
+
+        public static TransitionFragment newInstance(@LayoutRes int layout) {
+            final Bundle args = new Bundle();
+            args.putInt(ARG_LAYOUT_ID, layout);
+            final TransitionFragment fragment = new TransitionFragment();
+            fragment.setArguments(args);
+            return fragment;
+        }
+
+        public TransitionFragment() {
+            setEnterTransition(createTransition(TRANSITION_ENTER));
+            setExitTransition(createTransition(TRANSITION_EXIT));
+            setReenterTransition(createTransition(TRANSITION_REENTER));
+            setReturnTransition(createTransition(TRANSITION_RETURN));
+            setSharedElementEnterTransition(createTransition(TRANSITION_SHARED_ENTER));
+            setSharedElementReturnTransition(createTransition(TRANSITION_SHARED_RETURN));
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+                @Nullable Bundle savedInstanceState) {
+            return inflater.inflate(getArguments().getInt(ARG_LAYOUT_ID), container, false);
+        }
+
+        @Override
+        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+            mRed = view.findViewById(R.id.redSquare);
+            mGreen = view.findViewById(R.id.greenSquare);
+            mBlue = view.findViewById(R.id.blueSquare);
+            if (mRed != null) {
+                ViewCompat.setTransitionName(mRed, "red");
+            }
+            if (mGreen != null) {
+                ViewCompat.setTransitionName(mGreen, "green");
+            }
+            if (mBlue != null) {
+                ViewCompat.setTransitionName(mBlue, "blue");
+            }
+        }
+
+        private Transition createTransition(int type) {
+            final Transition.TransitionListener listener = mock(
+                    Transition.TransitionListener.class);
+            final AutoTransition transition = new AutoTransition();
+            transition.addListener(listener);
+            transition.setDuration(10);
+            mListeners.put(type, listener);
+            return transition;
+        }
+
+    }
+
+}
diff --git a/androidx/transition/GhostViewApi14.java b/androidx/transition/GhostViewApi14.java
new file mode 100644
index 0000000..fa577d9
--- /dev/null
+++ b/androidx/transition/GhostViewApi14.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.annotation.SuppressLint;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.ViewTreeObserver;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.core.view.ViewCompat;
+
+/**
+ * Backport of android.view.GhostView introduced in API level 21.
+ * <p>
+ * While the platform version uses ViewOverlay, this ghost view finds the closest FrameLayout in
+ * the hierarchy and adds itself there.
+ * <p>
+ * Since we cannot use RenderNode to delegate drawing, we instead use {@link View#draw(Canvas)} to
+ * draw the target view. We apply the same transformation matrix applied to the target view. For
+ * that, this view is sized as large as the parent FrameLayout (except padding) while the platform
+ * version becomes as large as the target view.
+ */
+@SuppressLint("ViewConstructor")
+class GhostViewApi14 extends View implements GhostViewImpl {
+
+    static GhostViewImpl addGhost(View view, ViewGroup viewGroup) {
+        GhostViewApi14 ghostView = getGhostView(view);
+        if (ghostView == null) {
+            FrameLayout frameLayout = findFrameLayout(viewGroup);
+            if (frameLayout == null) {
+                return null;
+            }
+            ghostView = new GhostViewApi14(view);
+            frameLayout.addView(ghostView);
+        }
+        ghostView.mReferences++;
+        return ghostView;
+    }
+
+    static void removeGhost(View view) {
+        GhostViewApi14 ghostView = getGhostView(view);
+        if (ghostView != null) {
+            ghostView.mReferences--;
+            if (ghostView.mReferences <= 0) {
+                ViewParent parent = ghostView.getParent();
+                if (parent instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) parent;
+                    group.endViewTransition(ghostView);
+                    group.removeView(ghostView);
+                }
+            }
+        }
+    }
+
+    /**
+     * Find the closest FrameLayout in the ascendant hierarchy from the specified {@code
+     * viewGroup}.
+     */
+    private static FrameLayout findFrameLayout(ViewGroup viewGroup) {
+        while (!(viewGroup instanceof FrameLayout)) {
+            ViewParent parent = viewGroup.getParent();
+            if (!(parent instanceof ViewGroup)) {
+                return null;
+            }
+            viewGroup = (ViewGroup) parent;
+        }
+        return (FrameLayout) viewGroup;
+    }
+
+    /** The target view */
+    final View mView;
+
+    /** The parent of the view that is disappearing at the beginning of the animation */
+    ViewGroup mStartParent;
+
+    /** The view that is disappearing at the beginning of the animation */
+    View mStartView;
+
+    /** The number of references to this ghost view */
+    int mReferences;
+
+    /** The horizontal distance from the ghost view to the target view */
+    private int mDeltaX;
+
+    /** The horizontal distance from the ghost view to the target view */
+    private int mDeltaY;
+
+    /** The current transformation matrix of the target view */
+    Matrix mCurrentMatrix;
+
+    /** The matrix applied to the ghost view canvas */
+    private final Matrix mMatrix = new Matrix();
+
+    private final ViewTreeObserver.OnPreDrawListener mOnPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
+                @Override
+                public boolean onPreDraw() {
+                    // The target view was invalidated; get the transformation.
+                    mCurrentMatrix = mView.getMatrix();
+                    // We draw the view.
+                    ViewCompat.postInvalidateOnAnimation(GhostViewApi14.this);
+                    if (mStartParent != null && mStartView != null) {
+                        mStartParent.endViewTransition(mStartView);
+                        ViewCompat.postInvalidateOnAnimation(mStartParent);
+                        mStartParent = null;
+                        mStartView = null;
+                    }
+                    return true;
+                }
+            };
+
+    GhostViewApi14(View view) {
+        super(view.getContext());
+        mView = view;
+        setLayerType(LAYER_TYPE_HARDWARE, null);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        setGhostView(mView, this);
+        // Calculate the deltas
+        final int[] location = new int[2];
+        final int[] viewLocation = new int[2];
+        getLocationOnScreen(location);
+        mView.getLocationOnScreen(viewLocation);
+        viewLocation[0] = (int) (viewLocation[0] - mView.getTranslationX());
+        viewLocation[1] = (int) (viewLocation[1] - mView.getTranslationY());
+        mDeltaX = viewLocation[0] - location[0];
+        mDeltaY = viewLocation[1] - location[1];
+        // Monitor invalidation of the target view.
+        mView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener);
+        // Make the target view invisible because we draw it instead.
+        mView.setVisibility(INVISIBLE);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener);
+        mView.setVisibility(VISIBLE);
+        setGhostView(mView, null);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        // Apply the matrix while adjusting the coordinates
+        mMatrix.set(mCurrentMatrix);
+        mMatrix.postTranslate(mDeltaX, mDeltaY);
+        canvas.setMatrix(mMatrix);
+        // Draw the target
+        mView.draw(canvas);
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        mView.setVisibility(visibility == VISIBLE ? INVISIBLE : VISIBLE);
+    }
+
+    @Override
+    public void reserveEndViewTransition(ViewGroup viewGroup, View view) {
+        mStartParent = viewGroup;
+        mStartView = view;
+    }
+
+    private static void setGhostView(@NonNull View view, GhostViewApi14 ghostView) {
+        view.setTag(R.id.ghost_view, ghostView);
+    }
+
+    static GhostViewApi14 getGhostView(@NonNull View view) {
+        return (GhostViewApi14) view.getTag(R.id.ghost_view);
+    }
+
+}
diff --git a/androidx/transition/GhostViewApi21.java b/androidx/transition/GhostViewApi21.java
new file mode 100644
index 0000000..4cf5ae8
--- /dev/null
+++ b/androidx/transition/GhostViewApi21.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@RequiresApi(21)
+class GhostViewApi21 implements GhostViewImpl {
+
+    private static final String TAG = "GhostViewApi21";
+
+    private static Class<?> sGhostViewClass;
+    private static boolean sGhostViewClassFetched;
+    private static Method sAddGhostMethod;
+    private static boolean sAddGhostMethodFetched;
+    private static Method sRemoveGhostMethod;
+    private static boolean sRemoveGhostMethodFetched;
+
+    static GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+        fetchAddGhostMethod();
+        if (sAddGhostMethod != null) {
+            try {
+                return new GhostViewApi21(
+                        (View) sAddGhostMethod.invoke(null, view, viewGroup, matrix));
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+        return null;
+    }
+
+    static void removeGhost(View view) {
+        fetchRemoveGhostMethod();
+        if (sRemoveGhostMethod != null) {
+            try {
+                sRemoveGhostMethod.invoke(null, view);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+    }
+
+    /** A handle to the platform android.view.GhostView. */
+    private final View mGhostView;
+
+    private GhostViewApi21(@NonNull View ghostView) {
+        mGhostView = ghostView;
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        mGhostView.setVisibility(visibility);
+    }
+
+    @Override
+    public void reserveEndViewTransition(ViewGroup viewGroup, View view) {
+        // No need
+    }
+
+    private static void fetchGhostViewClass() {
+        if (!sGhostViewClassFetched) {
+            try {
+                sGhostViewClass = Class.forName("android.view.GhostView");
+            } catch (ClassNotFoundException e) {
+                Log.i(TAG, "Failed to retrieve GhostView class", e);
+            }
+            sGhostViewClassFetched = true;
+        }
+    }
+
+    private static void fetchAddGhostMethod() {
+        if (!sAddGhostMethodFetched) {
+            try {
+                fetchGhostViewClass();
+                sAddGhostMethod = sGhostViewClass.getDeclaredMethod("addGhost", View.class,
+                        ViewGroup.class, Matrix.class);
+                sAddGhostMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve addGhost method", e);
+            }
+            sAddGhostMethodFetched = true;
+        }
+    }
+
+    private static void fetchRemoveGhostMethod() {
+        if (!sRemoveGhostMethodFetched) {
+            try {
+                fetchGhostViewClass();
+                sRemoveGhostMethod = sGhostViewClass.getDeclaredMethod("removeGhost", View.class);
+                sRemoveGhostMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve removeGhost method", e);
+            }
+            sRemoveGhostMethodFetched = true;
+        }
+    }
+
+}
diff --git a/androidx/transition/GhostViewImpl.java b/androidx/transition/GhostViewImpl.java
new file mode 100644
index 0000000..5a4d6cf
--- /dev/null
+++ b/androidx/transition/GhostViewImpl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+interface GhostViewImpl {
+
+    void setVisibility(int visibility);
+
+    /**
+     * Reserves a call to {@link ViewGroup#endViewTransition(View)} at the time when the GhostView
+     * starts drawing its real view.
+     */
+    void reserveEndViewTransition(ViewGroup viewGroup, View view);
+
+}
diff --git a/androidx/transition/GhostViewUtils.java b/androidx/transition/GhostViewUtils.java
new file mode 100644
index 0000000..9e460ca
--- /dev/null
+++ b/androidx/transition/GhostViewUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.os.Build;
+import android.view.View;
+import android.view.ViewGroup;
+
+class GhostViewUtils {
+
+    static GhostViewImpl addGhost(View view, ViewGroup viewGroup, Matrix matrix) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            return GhostViewApi21.addGhost(view, viewGroup, matrix);
+        }
+        return GhostViewApi14.addGhost(view, viewGroup);
+    }
+
+    static void removeGhost(View view) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            GhostViewApi21.removeGhost(view);
+        } else {
+            GhostViewApi14.removeGhost(view);
+        }
+    }
+
+    private GhostViewUtils() {
+    }
+}
diff --git a/androidx/transition/ImageViewUtils.java b/androidx/transition/ImageViewUtils.java
new file mode 100644
index 0000000..9d73e9e
--- /dev/null
+++ b/androidx/transition/ImageViewUtils.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Matrix;
+import android.os.Build;
+import android.util.Log;
+import android.widget.ImageView;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class ImageViewUtils {
+    private static final String TAG = "ImageViewUtils";
+
+    private static Method sAnimateTransformMethod;
+    private static boolean sAnimateTransformMethodFetched;
+
+    /**
+     * Starts animating the transformation of the image view. This has to be called before calling
+     * {@link #animateTransform(ImageView, Matrix)}.
+     */
+    static void startAnimateTransform(ImageView view) {
+        if (Build.VERSION.SDK_INT < 21) {
+            final ImageView.ScaleType scaleType = view.getScaleType();
+            view.setTag(R.id.save_scale_type, scaleType);
+            if (scaleType == ImageView.ScaleType.MATRIX) {
+                view.setTag(R.id.save_image_matrix, view.getImageMatrix());
+            } else {
+                view.setScaleType(ImageView.ScaleType.MATRIX);
+            }
+            view.setImageMatrix(MatrixUtils.IDENTITY_MATRIX);
+        }
+    }
+
+    /**
+     * Sets the matrix to animate the content of the image view.
+     */
+    static void animateTransform(ImageView view, Matrix matrix) {
+        if (Build.VERSION.SDK_INT < 21) {
+            view.setImageMatrix(matrix);
+        } else {
+            fetchAnimateTransformMethod();
+            if (sAnimateTransformMethod != null) {
+                try {
+                    sAnimateTransformMethod.invoke(view, matrix);
+                } catch (IllegalAccessException e) {
+                    // Do nothing
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException(e.getCause());
+                }
+            }
+        }
+    }
+
+    private static void fetchAnimateTransformMethod() {
+        if (!sAnimateTransformMethodFetched) {
+            try {
+                sAnimateTransformMethod = ImageView.class.getDeclaredMethod("animateTransform",
+                        Matrix.class);
+                sAnimateTransformMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve animateTransform method", e);
+            }
+            sAnimateTransformMethodFetched = true;
+        }
+    }
+
+    /**
+     * Reserves that the caller will stop calling {@link #animateTransform(ImageView, Matrix)} when
+     * the specified animator ends.
+     */
+    static void reserveEndAnimateTransform(final ImageView view, Animator animator) {
+        if (Build.VERSION.SDK_INT < 21) {
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    final ImageView.ScaleType scaleType = (ImageView.ScaleType)
+                            view.getTag(R.id.save_scale_type);
+                    view.setScaleType(scaleType);
+                    view.setTag(R.id.save_scale_type, null);
+                    if (scaleType == ImageView.ScaleType.MATRIX) {
+                        view.setImageMatrix((Matrix) view.getTag(R.id.save_image_matrix));
+                        view.setTag(R.id.save_image_matrix, null);
+                    }
+                    animation.removeListener(this);
+                }
+            });
+        }
+    }
+
+    private ImageViewUtils() {
+    }
+}
diff --git a/androidx/transition/MatrixUtils.java b/androidx/transition/MatrixUtils.java
new file mode 100644
index 0000000..9a13796
--- /dev/null
+++ b/androidx/transition/MatrixUtils.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.graphics.RectF;
+
+class MatrixUtils {
+
+    static final Matrix IDENTITY_MATRIX = new Matrix() {
+
+        void oops() {
+            throw new IllegalStateException("Matrix can not be modified");
+        }
+
+        @Override
+        public void set(Matrix src) {
+            oops();
+        }
+
+        @Override
+        public void reset() {
+            oops();
+        }
+
+        @Override
+        public void setTranslate(float dx, float dy) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setScale(float sx, float sy) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setRotate(float degrees) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSinCos(float sinValue, float cosValue) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky, float px, float py) {
+            oops();
+        }
+
+        @Override
+        public void setSkew(float kx, float ky) {
+            oops();
+        }
+
+        @Override
+        public boolean setConcat(Matrix a, Matrix b) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean preConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postTranslate(float dx, float dy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postScale(float sx, float sy) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postRotate(float degrees) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky, float px, float py) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postSkew(float kx, float ky) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean postConcat(Matrix other) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public boolean setPolyToPoly(float[] src, int srcIndex, float[] dst, int dstIndex,
+                int pointCount) {
+            oops();
+            return false;
+        }
+
+        @Override
+        public void setValues(float[] values) {
+            oops();
+        }
+
+    };
+
+    private MatrixUtils() {
+    }
+}
diff --git a/androidx/transition/ObjectAnimatorUtils.java b/androidx/transition/ObjectAnimatorUtils.java
new file mode 100644
index 0000000..bd73b58
--- /dev/null
+++ b/androidx/transition/ObjectAnimatorUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.ObjectAnimator;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.os.Build;
+import android.util.Property;
+
+class ObjectAnimatorUtils {
+
+    static <T> ObjectAnimator ofPointF(T target, Property<T, PointF> property, Path path) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            return ObjectAnimator.ofObject(target, property, null, path);
+        }
+        return ObjectAnimator.ofFloat(target, new PathProperty<>(property, path), 0f, 1f);
+    }
+
+    private ObjectAnimatorUtils() {
+    }
+}
diff --git a/androidx/transition/PathMotion.java b/androidx/transition/PathMotion.java
new file mode 100644
index 0000000..3e05665
--- /dev/null
+++ b/androidx/transition/PathMotion.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.content.Context;
+import android.graphics.Path;
+import android.util.AttributeSet;
+
+/**
+ * This base class can be extended to provide motion along a Path to Transitions.
+ *
+ * <p>
+ * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
+ * in a straight path between the start and end positions. Applications that desire to
+ * have these motions move in a curve can change how Views interpolate in two dimensions
+ * by extending PathMotion and implementing {@link #getPath(float, float, float, float)}.
+ * </p>
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>
+ * {@code
+ * <changeBounds>
+ *     <pathMotion class="my.app.transition.MyPathMotion"/>
+ * </changeBounds>
+ * }
+ * </pre>
+ */
+public abstract class PathMotion {
+
+    public PathMotion() {
+    }
+
+    public PathMotion(Context context, AttributeSet attrs) {
+    }
+
+    /**
+     * Provide a Path to interpolate between two points <code>(startX, startY)</code> and
+     * <code>(endX, endY)</code>. This allows controlled curved motion along two dimensions.
+     *
+     * @param startX The x coordinate of the starting point.
+     * @param startY The y coordinate of the starting point.
+     * @param endX   The x coordinate of the ending point.
+     * @param endY   The y coordinate of the ending point.
+     * @return A Path along which the points should be interpolated. The returned Path
+     * must start at point <code>(startX, startY)</code>, typically using
+     * {@link android.graphics.Path#moveTo(float, float)} and end at <code>(endX, endY)</code>.
+     */
+    public abstract Path getPath(float startX, float startY, float endX, float endY);
+}
diff --git a/androidx/transition/PathMotionTest.java b/androidx/transition/PathMotionTest.java
new file mode 100644
index 0000000..53ae77b
--- /dev/null
+++ b/androidx/transition/PathMotionTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+
+public abstract class PathMotionTest {
+
+    public static void assertPathMatches(Path expectedPath, Path path) {
+        PathMeasure expectedMeasure = new PathMeasure(expectedPath, false);
+        PathMeasure pathMeasure = new PathMeasure(path, false);
+
+        boolean expectedNextContour;
+        boolean pathNextContour;
+        int contourIndex = 0;
+        do {
+            float expectedLength = expectedMeasure.getLength();
+            assertEquals("Lengths differ", expectedLength, pathMeasure.getLength(), 0.01f);
+
+            float minLength = Math.min(expectedLength, pathMeasure.getLength());
+
+            float[] pos = new float[2];
+
+            float increment = minLength / 5f;
+            for (float along = 0; along <= minLength; along += increment) {
+                expectedMeasure.getPosTan(along, pos, null);
+                float expectedX = pos[0];
+                float expectedY = pos[1];
+
+                pathMeasure.getPosTan(along, pos, null);
+                assertEquals("Failed at " + increment + " in contour " + contourIndex,
+                        expectedX, pos[0], 0.01f);
+                assertEquals("Failed at " + increment + " in contour " + contourIndex,
+                        expectedY, pos[1], 0.01f);
+            }
+            expectedNextContour = expectedMeasure.nextContour();
+            pathNextContour = pathMeasure.nextContour();
+            contourIndex++;
+        } while (expectedNextContour && pathNextContour);
+        assertFalse(expectedNextContour);
+        assertFalse(pathNextContour);
+    }
+
+}
diff --git a/androidx/transition/PathProperty.java b/androidx/transition/PathProperty.java
new file mode 100644
index 0000000..be2dddb
--- /dev/null
+++ b/androidx/transition/PathProperty.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.PointF;
+import android.util.Property;
+
+/**
+ * A special {@link Property} that can animate a pair of properties bi-dimensionally along the
+ * specified path.
+ * <p>
+ * This property should always be used with Animator that sets float fractions between
+ * {@code 0.f} and {@code 1.f}. For example, setting {@code 0.5f} to this property sets the
+ * values right in the middle of the specified path to the underlying properties.
+ * <p>
+ * Unlike many of the platform built-in properties, instances of this class cannot be reused
+ * for later animations.
+ */
+class PathProperty<T> extends Property<T, Float> {
+
+    private final Property<T, PointF> mProperty;
+    private final PathMeasure mPathMeasure;
+    private final float mPathLength;
+    private final float[] mPosition = new float[2];
+    private final PointF mPointF = new PointF();
+    private float mCurrentFraction;
+
+    PathProperty(Property<T, PointF> property, Path path) {
+        super(Float.class, property.getName());
+        mProperty = property;
+        mPathMeasure = new PathMeasure(path, false);
+        mPathLength = mPathMeasure.getLength();
+    }
+
+    @Override
+    public Float get(T object) {
+        return mCurrentFraction;
+    }
+
+    @Override
+    public void set(T target, Float fraction) {
+        mCurrentFraction = fraction;
+        mPathMeasure.getPosTan(mPathLength * fraction, mPosition, null);
+        mPointF.x = mPosition[0];
+        mPointF.y = mPosition[1];
+        mProperty.set(target, mPointF);
+    }
+
+}
diff --git a/androidx/transition/PatternPathMotion.java b/androidx/transition/PatternPathMotion.java
new file mode 100644
index 0000000..7f6153d
--- /dev/null
+++ b/androidx/transition/PatternPathMotion.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.util.AttributeSet;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.graphics.PathParser;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * A PathMotion that takes a Path pattern and applies it to the separation between two points.
+ * The starting point of the Path will be moved to the origin and the end point will be scaled
+ * and rotated so that it matches with the target end point.
+ * <p>This may be used in XML as an element inside a transition.</p>
+ * <pre>{@code
+ * <changeBounds>
+ *     <patternPathMotion android:patternPathData="M0 0 L0 100 L100 100"/>
+ * </changeBounds>}
+ * </pre>
+ */
+public class PatternPathMotion extends PathMotion {
+
+    private Path mOriginalPatternPath;
+
+    private final Path mPatternPath = new Path();
+
+    private final Matrix mTempMatrix = new Matrix();
+
+    /**
+     * Constructs a PatternPathMotion with a straight-line pattern.
+     */
+    public PatternPathMotion() {
+        mPatternPath.lineTo(1, 0);
+        mOriginalPatternPath = mPatternPath;
+    }
+
+    public PatternPathMotion(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.PATTERN_PATH_MOTION);
+        try {
+            String pathData = TypedArrayUtils.getNamedString(a, (XmlPullParser) attrs,
+                    "patternPathData", Styleable.PatternPathMotion.PATTERN_PATH_DATA);
+            if (pathData == null) {
+                throw new RuntimeException("pathData must be supplied for patternPathMotion");
+            }
+            Path pattern = PathParser.createPathFromPathData(pathData);
+            setPatternPath(pattern);
+        } finally {
+            a.recycle();
+        }
+    }
+
+    /**
+     * Creates a PatternPathMotion with the Path defining a pattern of motion between two
+     * coordinates. The pattern will be translated, rotated, and scaled to fit between the start
+     * and end points. The pattern must not be empty and must have the end point differ from the
+     * start point.
+     *
+     * @param patternPath A Path to be used as a pattern for two-dimensional motion.
+     */
+    public PatternPathMotion(Path patternPath) {
+        setPatternPath(patternPath);
+    }
+
+    /**
+     * Returns the Path defining a pattern of motion between two coordinates.
+     * The pattern will be translated, rotated, and scaled to fit between the start and end points.
+     * The pattern must not be empty and must have the end point differ from the start point.
+     *
+     * @return the Path defining a pattern of motion between two coordinates.
+     */
+    public Path getPatternPath() {
+        return mOriginalPatternPath;
+    }
+
+    /**
+     * Sets the Path defining a pattern of motion between two coordinates.
+     * The pattern will be translated, rotated, and scaled to fit between the start and end points.
+     * The pattern must not be empty and must have the end point differ from the start point.
+     *
+     * @param patternPath A Path to be used as a pattern for two-dimensional motion.
+     */
+    public void setPatternPath(Path patternPath) {
+        PathMeasure pathMeasure = new PathMeasure(patternPath, false);
+        float length = pathMeasure.getLength();
+        float[] pos = new float[2];
+        pathMeasure.getPosTan(length, pos, null);
+        float endX = pos[0];
+        float endY = pos[1];
+        pathMeasure.getPosTan(0, pos, null);
+        float startX = pos[0];
+        float startY = pos[1];
+
+        if (startX == endX && startY == endY) {
+            throw new IllegalArgumentException("pattern must not end at the starting point");
+        }
+
+        mTempMatrix.setTranslate(-startX, -startY);
+        float dx = endX - startX;
+        float dy = endY - startY;
+        float distance = distance(dx, dy);
+        float scale = 1 / distance;
+        mTempMatrix.postScale(scale, scale);
+        double angle = Math.atan2(dy, dx);
+        mTempMatrix.postRotate((float) Math.toDegrees(-angle));
+        patternPath.transform(mTempMatrix, mPatternPath);
+        mOriginalPatternPath = patternPath;
+    }
+
+    @Override
+    public Path getPath(float startX, float startY, float endX, float endY) {
+        float dx = endX - startX;
+        float dy = endY - startY;
+        float length = distance(dx, dy);
+        double angle = Math.atan2(dy, dx);
+
+        mTempMatrix.setScale(length, length);
+        mTempMatrix.postRotate((float) Math.toDegrees(angle));
+        mTempMatrix.postTranslate(startX, startY);
+        Path path = new Path();
+        mPatternPath.transform(mTempMatrix, path);
+        return path;
+    }
+
+    private static float distance(float x, float y) {
+        return (float) Math.sqrt((x * x) + (y * y));
+    }
+
+}
diff --git a/androidx/transition/PatternPathMotionTest.java b/androidx/transition/PatternPathMotionTest.java
new file mode 100644
index 0000000..e4fe206
--- /dev/null
+++ b/androidx/transition/PatternPathMotionTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertSame;
+
+import android.graphics.Path;
+import android.graphics.RectF;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PatternPathMotionTest extends PathMotionTest {
+
+    @Test
+    public void testStraightPath() {
+        Path pattern = new Path();
+        pattern.moveTo(100, 500);
+        pattern.lineTo(300, 1000);
+
+        PatternPathMotion pathMotion = new PatternPathMotion(pattern);
+        assertPathMatches(pattern, pathMotion.getPatternPath());
+
+        Path expected = new Path();
+        expected.moveTo(0, 0);
+        expected.lineTo(100, 100);
+
+        assertPathMatches(expected, pathMotion.getPath(0, 0, 100, 100));
+    }
+
+    @Test
+    public void testCurve() {
+        RectF oval = new RectF();
+        Path pattern = new Path();
+        oval.set(0, 0, 100, 100);
+        pattern.addArc(oval, 0, 180);
+
+        PatternPathMotion pathMotion = new PatternPathMotion(pattern);
+        assertPathMatches(pattern, pathMotion.getPatternPath());
+
+        Path expected = new Path();
+        oval.set(-50, 0, 50, 100);
+        expected.addArc(oval, -90, 180);
+
+        assertPathMatches(expected, pathMotion.getPath(0, 0, 0, 100));
+    }
+
+    @Test
+    public void testSetPatternPath() {
+        Path pattern = new Path();
+        RectF oval = new RectF(0, 0, 100, 100);
+        pattern.addArc(oval, 0, 180);
+
+        PatternPathMotion patternPathMotion = new PatternPathMotion();
+        patternPathMotion.setPatternPath(pattern);
+        assertSame(pattern, patternPathMotion.getPatternPath());
+    }
+
+}
diff --git a/androidx/transition/PropagationTest.java b/androidx/transition/PropagationTest.java
new file mode 100644
index 0000000..8efb856
--- /dev/null
+++ b/androidx/transition/PropagationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class PropagationTest extends BaseTransitionTest {
+
+    @Test
+    public void testCircularPropagation() throws Throwable {
+        enterScene(R.layout.scene10);
+        CircularPropagation propagation = new CircularPropagation();
+        mTransition.setPropagation(propagation);
+        final TransitionValues redValues = new TransitionValues();
+        redValues.view = mRoot.findViewById(R.id.redSquare);
+        propagation.captureValues(redValues);
+
+        // Only the reported propagation properties are set
+        for (String prop : propagation.getPropagationProperties()) {
+            assertTrue(redValues.values.keySet().contains(prop));
+        }
+        assertEquals(propagation.getPropagationProperties().length, redValues.values.size());
+
+        // check the visibility
+        assertEquals(View.VISIBLE, propagation.getViewVisibility(redValues));
+        assertEquals(View.GONE, propagation.getViewVisibility(null));
+
+        // Check the positions
+        int[] pos = new int[2];
+        redValues.view.getLocationOnScreen(pos);
+        pos[0] += redValues.view.getWidth() / 2;
+        pos[1] += redValues.view.getHeight() / 2;
+        assertEquals(pos[0], propagation.getViewX(redValues));
+        assertEquals(pos[1], propagation.getViewY(redValues));
+
+        mTransition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(@NonNull Transition transition) {
+                return new Rect(0, 0, redValues.view.getWidth(), redValues.view.getHeight());
+            }
+        });
+
+        long redDelay = getDelay(R.id.redSquare);
+        // red square's delay should be roughly 0 since it is at the epicenter
+        assertEquals(0f, redDelay, 30f);
+
+        // The green square is on the upper-right
+        long greenDelay = getDelay(R.id.greenSquare);
+        assertTrue(greenDelay < redDelay);
+
+        // The blue square is on the lower-right
+        long blueDelay = getDelay(R.id.blueSquare);
+        assertTrue(blueDelay < greenDelay);
+
+        // Test propagation speed
+        propagation.setPropagationSpeed(1000000000f);
+        assertEquals(0, getDelay(R.id.blueSquare));
+    }
+
+    private TransitionValues capturePropagationValues(int viewId) {
+        TransitionValues transitionValues = new TransitionValues();
+        transitionValues.view = mRoot.findViewById(viewId);
+        TransitionPropagation propagation = mTransition.getPropagation();
+        assertNotNull(propagation);
+        propagation.captureValues(transitionValues);
+        return transitionValues;
+    }
+
+    private long getDelay(int viewId) {
+        TransitionValues transitionValues = capturePropagationValues(viewId);
+        TransitionPropagation propagation = mTransition.getPropagation();
+        assertNotNull(propagation);
+        return propagation.getStartDelay(mRoot, mTransition, transitionValues, null);
+    }
+
+}
diff --git a/androidx/transition/PropertyValuesHolderUtils.java b/androidx/transition/PropertyValuesHolderUtils.java
new file mode 100644
index 0000000..42527c4
--- /dev/null
+++ b/androidx/transition/PropertyValuesHolderUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.PropertyValuesHolder;
+import android.graphics.Path;
+import android.graphics.PointF;
+import android.os.Build;
+import android.util.Property;
+
+class PropertyValuesHolderUtils {
+
+    /**
+     * Constructs and returns a PropertyValuesHolder with a given property and
+     * a Path along which the values should be animated. This variant supports a
+     * <code>TypeConverter</code> to convert from <code>PointF</code> to the target
+     * type.
+     *
+     * @param property The property being animated. Should not be null.
+     * @param path     The Path along which the values should be animated.
+     * @return PropertyValuesHolder The constructed PropertyValuesHolder object.
+     */
+    static PropertyValuesHolder ofPointF(Property<?, PointF> property, Path path) {
+        if (Build.VERSION.SDK_INT >= 21) {
+            return PropertyValuesHolder.ofObject(property, null, path);
+        }
+        return PropertyValuesHolder.ofFloat(new PathProperty<>(property, path), 0f, 1f);
+    }
+
+    private PropertyValuesHolderUtils() {
+    }
+}
diff --git a/androidx/transition/RectEvaluator.java b/androidx/transition/RectEvaluator.java
new file mode 100644
index 0000000..6dab422
--- /dev/null
+++ b/androidx/transition/RectEvaluator.java
@@ -0,0 +1,86 @@
+/*
+ * 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 androidx.transition;
+
+import android.animation.TypeEvaluator;
+import android.graphics.Rect;
+
+/**
+ * This evaluator can be used to perform type interpolation between <code>Rect</code> values.
+ */
+class RectEvaluator implements TypeEvaluator<Rect> {
+
+    /**
+     * When null, a new Rect is returned on every evaluate call. When non-null,
+     * mRect will be modified and returned on every evaluate.
+     */
+    private Rect mRect;
+
+    /**
+     * Construct a RectEvaluator that returns a new Rect on every evaluate call.
+     * To avoid creating an object for each evaluate call,
+     * {@link RectEvaluator#RectEvaluator(android.graphics.Rect)} should be used
+     * whenever possible.
+     */
+    RectEvaluator() {
+    }
+
+    /**
+     * Constructs a RectEvaluator that modifies and returns <code>reuseRect</code>
+     * in {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} calls.
+     * The value returned from
+     * {@link #evaluate(float, android.graphics.Rect, android.graphics.Rect)} should
+     * not be cached because it will change over time as the object is reused on each
+     * call.
+     *
+     * @param reuseRect A Rect to be modified and returned by evaluate.
+     */
+    RectEvaluator(Rect reuseRect) {
+        mRect = reuseRect;
+    }
+
+    /**
+     * This function returns the result of linearly interpolating the start and
+     * end Rect values, with <code>fraction</code> representing the proportion
+     * between the start and end values. The calculation is a simple parametric
+     * calculation on each of the separate components in the Rect objects
+     * (left, top, right, and bottom).
+     *
+     * <p>If {@link #RectEvaluator(android.graphics.Rect)} was used to construct
+     * this RectEvaluator, the object returned will be the <code>reuseRect</code>
+     * passed into the constructor.</p>
+     *
+     * @param fraction   The fraction from the starting to the ending values
+     * @param startValue The start Rect
+     * @param endValue   The end Rect
+     * @return A linear interpolation between the start and end values, given the
+     * <code>fraction</code> parameter.
+     */
+    @Override
+    public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
+        int left = startValue.left + (int) ((endValue.left - startValue.left) * fraction);
+        int top = startValue.top + (int) ((endValue.top - startValue.top) * fraction);
+        int right = startValue.right + (int) ((endValue.right - startValue.right) * fraction);
+        int bottom = startValue.bottom + (int) ((endValue.bottom - startValue.bottom) * fraction);
+        if (mRect == null) {
+            return new Rect(left, top, right, bottom);
+        } else {
+            mRect.set(left, top, right, bottom);
+            return mRect;
+        }
+    }
+}
diff --git a/androidx/transition/Scene.java b/androidx/transition/Scene.java
new file mode 100644
index 0000000..9239ef6
--- /dev/null
+++ b/androidx/transition/Scene.java
@@ -0,0 +1,260 @@
+/*
+ * 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 androidx.transition;
+
+import android.content.Context;
+import android.util.SparseArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.LayoutRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+/**
+ * A scene represents the collection of values that various properties in the
+ * View hierarchy will have when the scene is applied. A Scene can be
+ * configured to automatically run a Transition when it is applied, which will
+ * animate the various property changes that take place during the
+ * scene change.
+ */
+public class Scene {
+
+    private Context mContext;
+    private int mLayoutId = -1;
+    private ViewGroup mSceneRoot;
+    private View mLayout; // alternative to layoutId
+    private Runnable mEnterAction, mExitAction;
+
+    /**
+     * Returns a Scene described by the resource file associated with the given
+     * <code>layoutId</code> parameter. If such a Scene has already been created for
+     * the given <code>sceneRoot</code>, that same Scene will be returned.
+     * This caching of layoutId-based scenes enables sharing of common scenes
+     * between those created in code and those referenced by {@link TransitionManager}
+     * XML resource files.
+     *
+     * @param sceneRoot The root of the hierarchy in which scene changes
+     *                  and transitions will take place.
+     * @param layoutId  The id of a standard layout resource file.
+     * @param context   The context used in the process of inflating
+     *                  the layout resource.
+     * @return The scene for the given root and layout id
+     */
+    @NonNull
+    public static Scene getSceneForLayout(@NonNull ViewGroup sceneRoot, @LayoutRes int layoutId,
+            @NonNull Context context) {
+        @SuppressWarnings("unchecked")
+        SparseArray<Scene> scenes =
+                (SparseArray<Scene>) sceneRoot.getTag(R.id.transition_scene_layoutid_cache);
+        if (scenes == null) {
+            scenes = new SparseArray<>();
+            sceneRoot.setTag(R.id.transition_scene_layoutid_cache, scenes);
+        }
+        Scene scene = scenes.get(layoutId);
+        if (scene != null) {
+            return scene;
+        } else {
+            scene = new Scene(sceneRoot, layoutId, context);
+            scenes.put(layoutId, scene);
+            return scene;
+        }
+    }
+
+    /**
+     * Constructs a Scene with no information about how values will change
+     * when this scene is applied. This constructor might be used when
+     * a Scene is created with the intention of being dynamically configured,
+     * through setting {@link #setEnterAction(Runnable)} and possibly
+     * {@link #setExitAction(Runnable)}.
+     *
+     * @param sceneRoot The root of the hierarchy in which scene changes
+     *                  and transitions will take place.
+     */
+    public Scene(@NonNull ViewGroup sceneRoot) {
+        mSceneRoot = sceneRoot;
+    }
+
+    /**
+     * Constructs a Scene which, when entered, will remove any
+     * children from the sceneRoot container and will inflate and add
+     * the hierarchy specified by the layoutId resource file.
+     *
+     * <p>This method is hidden because layoutId-based scenes should be
+     * created by the caching factory method {@link Scene#getCurrentScene(View)}.</p>
+     *
+     * @param sceneRoot The root of the hierarchy in which scene changes
+     *                  and transitions will take place.
+     * @param layoutId  The id of a resource file that defines the view
+     *                  hierarchy of this scene.
+     * @param context   The context used in the process of inflating
+     *                  the layout resource.
+     */
+    private Scene(ViewGroup sceneRoot, int layoutId, Context context) {
+        mContext = context;
+        mSceneRoot = sceneRoot;
+        mLayoutId = layoutId;
+    }
+
+    /**
+     * Constructs a Scene which, when entered, will remove any
+     * children from the sceneRoot container and add the layout
+     * object as a new child of that container.
+     *
+     * @param sceneRoot The root of the hierarchy in which scene changes
+     *                  and transitions will take place.
+     * @param layout    The view hierarchy of this scene, added as a child
+     *                  of sceneRoot when this scene is entered.
+     */
+    public Scene(@NonNull ViewGroup sceneRoot, @NonNull View layout) {
+        mSceneRoot = sceneRoot;
+        mLayout = layout;
+    }
+
+    /**
+     * Gets the root of the scene, which is the root of the view hierarchy
+     * affected by changes due to this scene, and which will be animated
+     * when this scene is entered.
+     *
+     * @return The root of the view hierarchy affected by this scene.
+     */
+    @NonNull
+    public ViewGroup getSceneRoot() {
+        return mSceneRoot;
+    }
+
+    /**
+     * Exits this scene, if it is the current scene
+     * on the scene's {@link #getSceneRoot() scene root}. The current scene is
+     * set when {@link #enter() entering} a scene.
+     * Exiting a scene runs the {@link #setExitAction(Runnable) exit action}
+     * if there is one.
+     */
+    public void exit() {
+        if (getCurrentScene(mSceneRoot) == this) {
+            if (mExitAction != null) {
+                mExitAction.run();
+            }
+        }
+    }
+
+    /**
+     * Enters this scene, which entails changing all values that
+     * are specified by this scene. These may be values associated
+     * with a layout view group or layout resource file which will
+     * now be added to the scene root, or it may be values changed by
+     * an {@link #setEnterAction(Runnable)} enter action}, or a
+     * combination of the these. No transition will be run when the
+     * scene is entered. To get transition behavior in scene changes,
+     * use one of the methods in {@link androidx.transition.TransitionManager} instead.
+     */
+    public void enter() {
+        // Apply layout change, if any
+        if (mLayoutId > 0 || mLayout != null) {
+            // empty out parent container before adding to it
+            getSceneRoot().removeAllViews();
+
+            if (mLayoutId > 0) {
+                LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
+            } else {
+                mSceneRoot.addView(mLayout);
+            }
+        }
+
+        // Notify next scene that it is entering. Subclasses may override to configure scene.
+        if (mEnterAction != null) {
+            mEnterAction.run();
+        }
+
+        setCurrentScene(mSceneRoot, this);
+    }
+
+    /**
+     * Set the scene that the given view is in. The current scene is set only
+     * on the root view of a scene, not for every view in that hierarchy. This
+     * information is used by Scene to determine whether there is a previous
+     * scene which should be exited before the new scene is entered.
+     *
+     * @param view The view on which the current scene is being set
+     */
+    static void setCurrentScene(View view, Scene scene) {
+        view.setTag(R.id.transition_current_scene, scene);
+    }
+
+    /**
+     * Gets the current {@link Scene} set on the given view. A scene is set on a view
+     * only if that view is the scene root.
+     *
+     * @return The current Scene set on this view. A value of null indicates that
+     * no Scene is currently set.
+     */
+    static Scene getCurrentScene(View view) {
+        return (Scene) view.getTag(R.id.transition_current_scene);
+    }
+
+    /**
+     * Scenes that are not defined with layout resources or
+     * hierarchies, or which need to perform additional steps
+     * after those hierarchies are changed to, should set an enter
+     * action, and possibly an exit action as well. An enter action
+     * will cause Scene to call back into application code to do
+     * anything else the application needs after transitions have
+     * captured pre-change values and after any other scene changes
+     * have been applied, such as the layout (if any) being added to
+     * the view hierarchy. After this method is called, Transitions will
+     * be played.
+     *
+     * @param action The runnable whose {@link Runnable#run() run()} method will
+     *               be called when this scene is entered
+     * @see #setExitAction(Runnable)
+     * @see androidx.transition.Scene(android.view.ViewGroup, android.view.ViewGroup)
+     */
+    public void setEnterAction(@Nullable Runnable action) {
+        mEnterAction = action;
+    }
+
+    /**
+     * Scenes that are not defined with layout resources or
+     * hierarchies, or which need to perform additional steps
+     * after those hierarchies are changed to, should set an enter
+     * action, and possibly an exit action as well. An exit action
+     * will cause Scene to call back into application code to do
+     * anything the application needs to do after applicable transitions have
+     * captured pre-change values, but before any other scene changes
+     * have been applied, such as the new layout (if any) being added to
+     * the view hierarchy. After this method is called, the next scene
+     * will be entered, including a call to {@link #setEnterAction(Runnable)}
+     * if an enter action is set.
+     *
+     * @see #setEnterAction(Runnable)
+     * @see androidx.transition.Scene(android.view.ViewGroup, android.view.ViewGroup)
+     */
+    public void setExitAction(@Nullable Runnable action) {
+        mExitAction = action;
+    }
+
+    /**
+     * Returns whether this Scene was created by a layout resource file, determined
+     * by the layoutId passed into
+     * {@link #getSceneForLayout(ViewGroup, int, Context)}.
+     */
+    boolean isCreatedFromLayoutResource() {
+        return (mLayoutId > 0);
+    }
+
+}
diff --git a/androidx/transition/SceneTest.java b/androidx/transition/SceneTest.java
new file mode 100644
index 0000000..2c63b98
--- /dev/null
+++ b/androidx/transition/SceneTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 androidx.transition;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class SceneTest extends BaseTest {
+
+    @Test
+    public void testGetSceneRoot() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        Scene scene = new Scene(root);
+        assertThat(scene.getSceneRoot(), is(sameInstance(root)));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testSceneWithViewGroup() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        FrameLayout layout = new FrameLayout(activity);
+        Scene scene = new Scene(root, layout);
+        CheckCalledRunnable enterAction = new CheckCalledRunnable();
+        CheckCalledRunnable exitAction = new CheckCalledRunnable();
+        scene.setEnterAction(enterAction);
+        scene.setExitAction(exitAction);
+        scene.enter();
+        assertThat(enterAction.wasCalled(), is(true));
+        assertThat(exitAction.wasCalled(), is(false));
+        assertThat(root.getChildCount(), is(1));
+        assertThat(root.getChildAt(0), is((View) layout));
+        scene.exit();
+        assertThat(exitAction.wasCalled(), is(true));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testSceneWithView() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        View view = new View(activity);
+        Scene scene = new Scene(root, view);
+        CheckCalledRunnable enterAction = new CheckCalledRunnable();
+        CheckCalledRunnable exitAction = new CheckCalledRunnable();
+        scene.setEnterAction(enterAction);
+        scene.setExitAction(exitAction);
+        scene.enter();
+        assertThat(enterAction.wasCalled(), is(true));
+        assertThat(exitAction.wasCalled(), is(false));
+        assertThat(root.getChildCount(), is(1));
+        assertThat(root.getChildAt(0), is(view));
+        scene.exit();
+        assertThat(exitAction.wasCalled(), is(true));
+    }
+
+    @Test
+    public void testEnterAction() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        Scene scene = new Scene(root);
+        CheckCalledRunnable runnable = new CheckCalledRunnable();
+        scene.setEnterAction(runnable);
+        scene.enter();
+        assertThat(runnable.wasCalled(), is(true));
+    }
+
+    @Test
+    public void testExitAction() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        Scene scene = new Scene(root);
+        scene.enter();
+        CheckCalledRunnable runnable = new CheckCalledRunnable();
+        scene.setExitAction(runnable);
+        scene.exit();
+        assertThat(runnable.wasCalled(), is(true));
+    }
+
+    @Test
+    public void testExitAction_withoutEnter() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        Scene scene = new Scene(root);
+        CheckCalledRunnable runnable = new CheckCalledRunnable();
+        scene.setExitAction(runnable);
+        scene.exit();
+        assertThat(runnable.wasCalled(), is(false));
+    }
+
+    @Test
+    public void testGetSceneForLayout_cache() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        Scene scene = Scene.getSceneForLayout(root, R.layout.support_scene0, activity);
+        assertThat("getSceneForLayout should return the same instance for subsequent calls",
+                Scene.getSceneForLayout(root, R.layout.support_scene0, activity),
+                is(sameInstance(scene)));
+    }
+
+}
diff --git a/androidx/transition/SidePropagation.java b/androidx/transition/SidePropagation.java
new file mode 100644
index 0000000..20d3b5d
--- /dev/null
+++ b/androidx/transition/SidePropagation.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.core.view.ViewCompat;
+
+/**
+ * A <code>TransitionPropagation</code> that propagates based on the distance to the side
+ * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
+ * the start of the transition, then it will transition sooner when closer to the side and
+ * later when farther. If the view is not visible in the start of the transition, then
+ * it will transition later when closer to the side and sooner when farther from the edge.
+ * This is the default TransitionPropagation used with {@link android.transition.Slide}.
+ */
+public class SidePropagation extends VisibilityPropagation {
+
+    private float mPropagationSpeed = 3.0f;
+    private int mSide = Gravity.BOTTOM;
+
+    /**
+     * Sets the side that is used to calculate the transition propagation. If the transitioning
+     * View is visible in the start of the transition, then it will transition sooner when
+     * closer to the side and later when farther. If the view is not visible in the start of
+     * the transition, then it will transition later when closer to the side and sooner when
+     * farther from the edge. The default is {@link Gravity#BOTTOM}.
+     *
+     * @param side The side that is used to calculate the transition propagation. Must be one of
+     *             {@link Gravity#LEFT}, {@link Gravity#TOP}, {@link Gravity#RIGHT},
+     *             {@link Gravity#BOTTOM}, {@link Gravity#START}, or {@link Gravity#END}.
+     */
+    public void setSide(@Slide.GravityFlag int side) {
+        mSide = side;
+    }
+
+    /**
+     * Sets the speed at which transition propagation happens, relative to the duration of the
+     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
+     * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
+     * in start delay of approximately the duration of the Transition. A speed of 2 means the
+     * start delay difference will be approximately half of the duration of the transition. A
+     * value of 0 is illegal, but negative values will invert the propagation.
+     *
+     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+     *                         of the transition. A speed of 4 means it works 4 times as fast
+     *                         as the duration of the transition. May not be 0.
+     */
+    public void setPropagationSpeed(float propagationSpeed) {
+        if (propagationSpeed == 0) {
+            throw new IllegalArgumentException("propagationSpeed may not be 0");
+        }
+        mPropagationSpeed = propagationSpeed;
+    }
+
+    @Override
+    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null && endValues == null) {
+            return 0;
+        }
+        int directionMultiplier = 1;
+        Rect epicenter = transition.getEpicenter();
+        TransitionValues positionValues;
+        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+            positionValues = startValues;
+            directionMultiplier = -1;
+        } else {
+            positionValues = endValues;
+        }
+
+        int viewCenterX = getViewX(positionValues);
+        int viewCenterY = getViewY(positionValues);
+
+        int[] loc = new int[2];
+        sceneRoot.getLocationOnScreen(loc);
+        int left = loc[0] + Math.round(sceneRoot.getTranslationX());
+        int top = loc[1] + Math.round(sceneRoot.getTranslationY());
+        int right = left + sceneRoot.getWidth();
+        int bottom = top + sceneRoot.getHeight();
+
+        int epicenterX;
+        int epicenterY;
+        if (epicenter != null) {
+            epicenterX = epicenter.centerX();
+            epicenterY = epicenter.centerY();
+        } else {
+            epicenterX = (left + right) / 2;
+            epicenterY = (top + bottom) / 2;
+        }
+
+        float distance = distance(sceneRoot, viewCenterX, viewCenterY, epicenterX, epicenterY,
+                left, top, right, bottom);
+        float maxDistance = getMaxDistance(sceneRoot);
+        float distanceFraction = distance / maxDistance;
+
+        long duration = transition.getDuration();
+        if (duration < 0) {
+            duration = 300;
+        }
+
+        return Math.round(duration * directionMultiplier / mPropagationSpeed * distanceFraction);
+    }
+
+    private int distance(View sceneRoot, int viewX, int viewY, int epicenterX, int epicenterY,
+            int left, int top, int right, int bottom) {
+        final int side;
+        if (mSide == Gravity.START) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            side = isRtl ? Gravity.RIGHT : Gravity.LEFT;
+        } else if (mSide == Gravity.END) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            side = isRtl ? Gravity.LEFT : Gravity.RIGHT;
+        } else {
+            side = mSide;
+        }
+        int distance = 0;
+        switch (side) {
+            case Gravity.LEFT:
+                distance = right - viewX + Math.abs(epicenterY - viewY);
+                break;
+            case Gravity.TOP:
+                distance = bottom - viewY + Math.abs(epicenterX - viewX);
+                break;
+            case Gravity.RIGHT:
+                distance = viewX - left + Math.abs(epicenterY - viewY);
+                break;
+            case Gravity.BOTTOM:
+                distance = viewY - top + Math.abs(epicenterX - viewX);
+                break;
+        }
+        return distance;
+    }
+
+    private int getMaxDistance(ViewGroup sceneRoot) {
+        switch (mSide) {
+            case Gravity.LEFT:
+            case Gravity.RIGHT:
+            case Gravity.START:
+            case Gravity.END:
+                return sceneRoot.getWidth();
+            default:
+                return sceneRoot.getHeight();
+        }
+    }
+
+}
diff --git a/androidx/transition/Slide.java b/androidx/transition/Slide.java
new file mode 100644
index 0000000..b38dc29
--- /dev/null
+++ b/androidx/transition/Slide.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.view.ViewCompat;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from one of the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ */
+public class Slide extends Visibility {
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+    private static final String PROPNAME_SCREEN_POSITION = "android:slide:screenPosition";
+    private CalculateSlide mSlideCalculator = sCalculateBottom;
+    private int mSlideEdge = Gravity.BOTTOM;
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END})
+    public @interface GravityFlag {
+    }
+
+    private interface CalculateSlide {
+
+        /** Returns the translation value for view when it goes out of the scene */
+        float getGoneX(ViewGroup sceneRoot, View view);
+
+        /** Returns the translation value for view when it goes out of the scene */
+        float getGoneY(ViewGroup sceneRoot, View view);
+    }
+
+    private abstract static class CalculateSlideHorizontal implements CalculateSlide {
+
+        @Override
+        public float getGoneY(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY();
+        }
+    }
+
+    private abstract static class CalculateSlideVertical implements CalculateSlide {
+
+        @Override
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX();
+        }
+    }
+
+    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+        @Override
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX() - sceneRoot.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateStart = new CalculateSlideHorizontal() {
+        @Override
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            final float x;
+            if (isRtl) {
+                x = view.getTranslationX() + sceneRoot.getWidth();
+            } else {
+                x = view.getTranslationX() - sceneRoot.getWidth();
+            }
+            return x;
+        }
+    };
+
+    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+        @Override
+        public float getGoneY(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY() - sceneRoot.getHeight();
+        }
+    };
+
+    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+        @Override
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX() + sceneRoot.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateEnd = new CalculateSlideHorizontal() {
+        @Override
+        public float getGoneX(ViewGroup sceneRoot, View view) {
+            final boolean isRtl = ViewCompat.getLayoutDirection(sceneRoot)
+                    == ViewCompat.LAYOUT_DIRECTION_RTL;
+            final float x;
+            if (isRtl) {
+                x = view.getTranslationX() - sceneRoot.getWidth();
+            } else {
+                x = view.getTranslationX() + sceneRoot.getWidth();
+            }
+            return x;
+        }
+    };
+
+    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+        @Override
+        public float getGoneY(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY() + sceneRoot.getHeight();
+        }
+    };
+
+    /**
+     * Constructor using the default {@link Gravity#BOTTOM}
+     * slide edge direction.
+     */
+    public Slide() {
+        setSlideEdge(Gravity.BOTTOM);
+    }
+
+    /**
+     * Constructor using the provided slide edge direction.
+     */
+    public Slide(int slideEdge) {
+        setSlideEdge(slideEdge);
+    }
+
+    public Slide(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.SLIDE);
+        int edge = TypedArrayUtils.getNamedInt(a, (XmlPullParser) attrs, "slideEdge",
+                Styleable.Slide.SLIDE_EDGE, Gravity.BOTTOM);
+        a.recycle();
+        //noinspection WrongConstant
+        setSlideEdge(edge);
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        int[] position = new int[2];
+        view.getLocationOnScreen(position);
+        transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    /**
+     * Change the edge that Views appear and disappear from.
+     *
+     * @param slideEdge The edge of the scene to use for Views appearing and disappearing. One of
+     *                  {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+     *                  {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+     *                  {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+     */
+    public void setSlideEdge(@GravityFlag int slideEdge) {
+        switch (slideEdge) {
+            case Gravity.LEFT:
+                mSlideCalculator = sCalculateLeft;
+                break;
+            case Gravity.TOP:
+                mSlideCalculator = sCalculateTop;
+                break;
+            case Gravity.RIGHT:
+                mSlideCalculator = sCalculateRight;
+                break;
+            case Gravity.BOTTOM:
+                mSlideCalculator = sCalculateBottom;
+                break;
+            case Gravity.START:
+                mSlideCalculator = sCalculateStart;
+                break;
+            case Gravity.END:
+                mSlideCalculator = sCalculateEnd;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid slide direction");
+        }
+        mSlideEdge = slideEdge;
+        SidePropagation propagation = new SidePropagation();
+        propagation.setSide(slideEdge);
+        setPropagation(propagation);
+    }
+
+    /**
+     * Returns the edge that Views appear and disappear from.
+     *
+     * @return the edge of the scene to use for Views appearing and disappearing. One of
+     * {@link android.view.Gravity#LEFT}, {@link android.view.Gravity#TOP},
+     * {@link android.view.Gravity#RIGHT}, {@link android.view.Gravity#BOTTOM},
+     * {@link android.view.Gravity#START}, {@link android.view.Gravity#END}.
+     */
+    @GravityFlag
+    public int getSlideEdge() {
+        return mSlideEdge;
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+        int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
+        float endX = view.getTranslationX();
+        float endY = view.getTranslationY();
+        float startX = mSlideCalculator.getGoneX(sceneRoot, view);
+        float startY = mSlideCalculator.getGoneY(sceneRoot, view);
+        return TranslationAnimationCreator
+                .createAnimation(view, endValues, position[0], position[1],
+                        startX, startY, endX, endY, sDecelerate);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null) {
+            return null;
+        }
+        int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
+        float startX = view.getTranslationX();
+        float startY = view.getTranslationY();
+        float endX = mSlideCalculator.getGoneX(sceneRoot, view);
+        float endY = mSlideCalculator.getGoneY(sceneRoot, view);
+        return TranslationAnimationCreator
+                .createAnimation(view, startValues, position[0], position[1],
+                        startX, startY, endX, endY, sAccelerate);
+    }
+
+}
diff --git a/androidx/transition/SlideBadEdgeTest.java b/androidx/transition/SlideBadEdgeTest.java
new file mode 100644
index 0000000..7e7c983
--- /dev/null
+++ b/androidx/transition/SlideBadEdgeTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Gravity;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SlideBadEdgeTest {
+
+    private static final Object[][] sBadGravity = {
+            {Gravity.AXIS_CLIP, "AXIS_CLIP"},
+            {Gravity.AXIS_PULL_AFTER, "AXIS_PULL_AFTER"},
+            {Gravity.AXIS_PULL_BEFORE, "AXIS_PULL_BEFORE"},
+            {Gravity.AXIS_SPECIFIED, "AXIS_SPECIFIED"},
+            {Gravity.AXIS_Y_SHIFT, "AXIS_Y_SHIFT"},
+            {Gravity.AXIS_X_SHIFT, "AXIS_X_SHIFT"},
+            {Gravity.CENTER, "CENTER"},
+            {Gravity.CLIP_VERTICAL, "CLIP_VERTICAL"},
+            {Gravity.CLIP_HORIZONTAL, "CLIP_HORIZONTAL"},
+            {Gravity.CENTER_VERTICAL, "CENTER_VERTICAL"},
+            {Gravity.CENTER_HORIZONTAL, "CENTER_HORIZONTAL"},
+            {Gravity.DISPLAY_CLIP_VERTICAL, "DISPLAY_CLIP_VERTICAL"},
+            {Gravity.DISPLAY_CLIP_HORIZONTAL, "DISPLAY_CLIP_HORIZONTAL"},
+            {Gravity.FILL_VERTICAL, "FILL_VERTICAL"},
+            {Gravity.FILL, "FILL"},
+            {Gravity.FILL_HORIZONTAL, "FILL_HORIZONTAL"},
+            {Gravity.HORIZONTAL_GRAVITY_MASK, "HORIZONTAL_GRAVITY_MASK"},
+            {Gravity.NO_GRAVITY, "NO_GRAVITY"},
+            {Gravity.RELATIVE_LAYOUT_DIRECTION, "RELATIVE_LAYOUT_DIRECTION"},
+            {Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK, "RELATIVE_HORIZONTAL_GRAVITY_MASK"},
+            {Gravity.VERTICAL_GRAVITY_MASK, "VERTICAL_GRAVITY_MASK"},
+    };
+
+    @Test
+    public void testBadSide() {
+        for (int i = 0; i < sBadGravity.length; i++) {
+            int badEdge = (Integer) sBadGravity[i][0];
+            String edgeName = (String) sBadGravity[i][1];
+            try {
+                new Slide(badEdge);
+                fail("Should not be able to set slide edge to " + edgeName);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                Slide slide = new Slide();
+                slide.setSlideEdge(badEdge);
+                fail("Should not be able to set slide edge to " + edgeName);
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+}
diff --git a/androidx/transition/SlideDefaultEdgeTest.java b/androidx/transition/SlideDefaultEdgeTest.java
new file mode 100644
index 0000000..03b14eb
--- /dev/null
+++ b/androidx/transition/SlideDefaultEdgeTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.Gravity;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SlideDefaultEdgeTest {
+
+    @Test
+    public void testDefaultSide() {
+        // default to bottom
+        Slide slide = new Slide();
+        assertEquals(Gravity.BOTTOM, slide.getSlideEdge());
+    }
+
+}
diff --git a/androidx/transition/SlideEdgeTest.java b/androidx/transition/SlideEdgeTest.java
new file mode 100644
index 0000000..8f08288
--- /dev/null
+++ b/androidx/transition/SlideEdgeTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
+import android.support.test.filters.MediumTest;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+@MediumTest
+public class SlideEdgeTest extends BaseTransitionTest {
+
+    private static final Object[][] sSlideEdgeArray = {
+            {Gravity.START, "START"},
+            {Gravity.END, "END"},
+            {Gravity.LEFT, "LEFT"},
+            {Gravity.TOP, "TOP"},
+            {Gravity.RIGHT, "RIGHT"},
+            {Gravity.BOTTOM, "BOTTOM"},
+    };
+
+    @Test
+    public void testSetSide() throws Throwable {
+        for (int i = 0; i < sSlideEdgeArray.length; i++) {
+            int slideEdge = (Integer) (sSlideEdgeArray[i][0]);
+            String edgeName = (String) (sSlideEdgeArray[i][1]);
+            Slide slide = new Slide(slideEdge);
+            assertEquals("Edge not set properly in constructor " + edgeName,
+                    slideEdge, slide.getSlideEdge());
+
+            slide = new Slide();
+            slide.setSlideEdge(slideEdge);
+            assertEquals("Edge not set properly with setter " + edgeName,
+                    slideEdge, slide.getSlideEdge());
+        }
+    }
+
+    @LargeTest
+    @Test
+    public void testSlideOut() throws Throwable {
+        for (int i = 0; i < sSlideEdgeArray.length; i++) {
+            final int slideEdge = (Integer) (sSlideEdgeArray[i][0]);
+            final Slide slide = new Slide(slideEdge);
+            final Transition.TransitionListener listener =
+                    mock(Transition.TransitionListener.class);
+            slide.addListener(listener);
+
+            rule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    rule.getActivity().setContentView(R.layout.scene1);
+                }
+            });
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+            final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+            final View hello = rule.getActivity().findViewById(R.id.hello);
+            final ViewGroup sceneRoot = (ViewGroup) rule.getActivity().findViewById(R.id.holder);
+
+            rule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    TransitionManager.beginDelayedTransition(sceneRoot, slide);
+                    redSquare.setVisibility(View.INVISIBLE);
+                    greenSquare.setVisibility(View.INVISIBLE);
+                    hello.setVisibility(View.INVISIBLE);
+                }
+            });
+            verify(listener, timeout(1000)).onTransitionStart(any(Transition.class));
+            verify(listener, never()).onTransitionEnd(any(Transition.class));
+            assertEquals(View.VISIBLE, redSquare.getVisibility());
+            assertEquals(View.VISIBLE, greenSquare.getVisibility());
+            assertEquals(View.VISIBLE, hello.getVisibility());
+
+            float redStartX = redSquare.getTranslationX();
+            float redStartY = redSquare.getTranslationY();
+
+            Thread.sleep(200);
+            verifyTranslation(slideEdge, redSquare);
+            verifyTranslation(slideEdge, greenSquare);
+            verifyTranslation(slideEdge, hello);
+
+            final float redMidX = redSquare.getTranslationX();
+            final float redMidY = redSquare.getTranslationY();
+
+            switch (slideEdge) {
+                case Gravity.LEFT:
+                case Gravity.START:
+                    assertTrue(
+                            "isn't sliding out to left. Expecting " + redStartX + " > " + redMidX,
+                            redStartX > redMidX);
+                    break;
+                case Gravity.RIGHT:
+                case Gravity.END:
+                    assertTrue(
+                            "isn't sliding out to right. Expecting " + redStartX + " < " + redMidX,
+                            redStartX < redMidX);
+                    break;
+                case Gravity.TOP:
+                    assertTrue("isn't sliding out to top. Expecting " + redStartY + " > " + redMidY,
+                            redStartY > redSquare.getTranslationY());
+                    break;
+                case Gravity.BOTTOM:
+                    assertTrue(
+                            "isn't sliding out to bottom. Expecting " + redStartY + " < " + redMidY,
+                            redStartY < redSquare.getTranslationY());
+                    break;
+            }
+            verify(listener, timeout(1000)).onTransitionEnd(any(Transition.class));
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            verifyNoTranslation(redSquare);
+            verifyNoTranslation(greenSquare);
+            verifyNoTranslation(hello);
+            assertEquals(View.INVISIBLE, redSquare.getVisibility());
+            assertEquals(View.INVISIBLE, greenSquare.getVisibility());
+            assertEquals(View.INVISIBLE, hello.getVisibility());
+        }
+    }
+
+    @LargeTest
+    @Test
+    public void testSlideIn() throws Throwable {
+        for (int i = 0; i < sSlideEdgeArray.length; i++) {
+            final int slideEdge = (Integer) (sSlideEdgeArray[i][0]);
+            final Slide slide = new Slide(slideEdge);
+            final Transition.TransitionListener listener =
+                    mock(Transition.TransitionListener.class);
+            slide.addListener(listener);
+
+            rule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    rule.getActivity().setContentView(R.layout.scene1);
+                }
+            });
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            final View redSquare = rule.getActivity().findViewById(R.id.redSquare);
+            final View greenSquare = rule.getActivity().findViewById(R.id.greenSquare);
+            final View hello = rule.getActivity().findViewById(R.id.hello);
+            final ViewGroup sceneRoot = (ViewGroup) rule.getActivity().findViewById(R.id.holder);
+
+            rule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    redSquare.setVisibility(View.INVISIBLE);
+                    greenSquare.setVisibility(View.INVISIBLE);
+                    hello.setVisibility(View.INVISIBLE);
+                }
+            });
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            // now slide in
+            rule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    TransitionManager.beginDelayedTransition(sceneRoot, slide);
+                    redSquare.setVisibility(View.VISIBLE);
+                    greenSquare.setVisibility(View.VISIBLE);
+                    hello.setVisibility(View.VISIBLE);
+                }
+            });
+            verify(listener, timeout(1000)).onTransitionStart(any(Transition.class));
+
+            verify(listener, never()).onTransitionEnd(any(Transition.class));
+            assertEquals(View.VISIBLE, redSquare.getVisibility());
+            assertEquals(View.VISIBLE, greenSquare.getVisibility());
+            assertEquals(View.VISIBLE, hello.getVisibility());
+
+            final float redStartX = redSquare.getTranslationX();
+            final float redStartY = redSquare.getTranslationY();
+
+            Thread.sleep(200);
+            verifyTranslation(slideEdge, redSquare);
+            verifyTranslation(slideEdge, greenSquare);
+            verifyTranslation(slideEdge, hello);
+            final float redMidX = redSquare.getTranslationX();
+            final float redMidY = redSquare.getTranslationY();
+
+            switch (slideEdge) {
+                case Gravity.LEFT:
+                case Gravity.START:
+                    assertTrue(
+                            "isn't sliding in from left. Expecting " + redStartX + " < " + redMidX,
+                            redStartX < redMidX);
+                    break;
+                case Gravity.RIGHT:
+                case Gravity.END:
+                    assertTrue(
+                            "isn't sliding in from right. Expecting " + redStartX + " > " + redMidX,
+                            redStartX > redMidX);
+                    break;
+                case Gravity.TOP:
+                    assertTrue(
+                            "isn't sliding in from top. Expecting " + redStartY + " < " + redMidY,
+                            redStartY < redSquare.getTranslationY());
+                    break;
+                case Gravity.BOTTOM:
+                    assertTrue("isn't sliding in from bottom. Expecting " + redStartY + " > "
+                                    + redMidY,
+                            redStartY > redSquare.getTranslationY());
+                    break;
+            }
+            verify(listener, timeout(1000)).onTransitionEnd(any(Transition.class));
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            verifyNoTranslation(redSquare);
+            verifyNoTranslation(greenSquare);
+            verifyNoTranslation(hello);
+            assertEquals(View.VISIBLE, redSquare.getVisibility());
+            assertEquals(View.VISIBLE, greenSquare.getVisibility());
+            assertEquals(View.VISIBLE, hello.getVisibility());
+        }
+    }
+
+    private void verifyTranslation(int slideEdge, View view) {
+        switch (slideEdge) {
+            case Gravity.LEFT:
+            case Gravity.START:
+                assertTrue(view.getTranslationX() < 0);
+                assertEquals(0f, view.getTranslationY(), 0.01f);
+                break;
+            case Gravity.RIGHT:
+            case Gravity.END:
+                assertTrue(view.getTranslationX() > 0);
+                assertEquals(0f, view.getTranslationY(), 0.01f);
+                break;
+            case Gravity.TOP:
+                assertTrue(view.getTranslationY() < 0);
+                assertEquals(0f, view.getTranslationX(), 0.01f);
+                break;
+            case Gravity.BOTTOM:
+                assertTrue(view.getTranslationY() > 0);
+                assertEquals(0f, view.getTranslationX(), 0.01f);
+                break;
+        }
+    }
+
+    private void verifyNoTranslation(View view) {
+        assertEquals(0f, view.getTranslationX(), 0.01f);
+        assertEquals(0f, view.getTranslationY(), 0.01f);
+    }
+
+}
diff --git a/androidx/transition/Styleable.java b/androidx/transition/Styleable.java
new file mode 100644
index 0000000..40f3cca
--- /dev/null
+++ b/androidx/transition/Styleable.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.annotation.SuppressLint;
+
+import androidx.annotation.StyleableRes;
+
+/**
+ * Copies of styleable ID values generated in the platform R.java.
+ */
+@SuppressLint("InlinedApi")
+class Styleable {
+
+    @StyleableRes
+    static final int[] TRANSITION_TARGET = {
+            android.R.attr.targetClass,
+            android.R.attr.targetId,
+            android.R.attr.excludeId,
+            android.R.attr.excludeClass,
+            android.R.attr.targetName,
+            android.R.attr.excludeName,
+    };
+
+    interface TransitionTarget {
+        @StyleableRes
+        int TARGET_CLASS = 0;
+        @StyleableRes
+        int TARGET_ID = 1;
+        @StyleableRes
+        int EXCLUDE_ID = 2;
+        @StyleableRes
+        int EXCLUDE_CLASS = 3;
+        @StyleableRes
+        int TARGET_NAME = 4;
+        @StyleableRes
+        int EXCLUDE_NAME = 5;
+    }
+
+    @StyleableRes
+    static final int[] TRANSITION_MANAGER = {
+            android.R.attr.fromScene,
+            android.R.attr.toScene,
+            android.R.attr.transition,
+    };
+
+    interface TransitionManager {
+        @StyleableRes
+        int FROM_SCENE = 0;
+        @StyleableRes
+        int TO_SCENE = 1;
+        @StyleableRes
+        int TRANSITION = 2;
+    }
+
+    @StyleableRes
+    static final int[] TRANSITION = {
+            android.R.attr.interpolator,
+            android.R.attr.duration,
+            android.R.attr.startDelay,
+            android.R.attr.matchOrder,
+    };
+
+    interface Transition {
+        @StyleableRes
+        int INTERPOLATOR = 0;
+        @StyleableRes
+        int DURATION = 1;
+        @StyleableRes
+        int START_DELAY = 2;
+        @StyleableRes
+        int MATCH_ORDER = 3;
+    }
+
+    @StyleableRes
+    static final int[] CHANGE_BOUNDS = {
+            android.R.attr.resizeClip,
+    };
+
+    interface ChangeBounds {
+        @StyleableRes
+        int RESIZE_CLIP = 0;
+    }
+
+    @StyleableRes
+    static final int[] VISIBILITY_TRANSITION = {
+            android.R.attr.transitionVisibilityMode,
+    };
+
+    interface VisibilityTransition {
+        @StyleableRes
+        int TRANSITION_VISIBILITY_MODE = 0;
+    }
+
+    @StyleableRes
+    static final int[] FADE = {
+            android.R.attr.fadingMode,
+    };
+
+    interface Fade {
+        @StyleableRes
+        int FADING_MODE = 0;
+    }
+
+    @StyleableRes
+    static final int[] CHANGE_TRANSFORM = {
+            android.R.attr.reparent,
+            android.R.attr.reparentWithOverlay,
+    };
+
+    interface ChangeTransform {
+        @StyleableRes
+        int REPARENT = 0;
+        @StyleableRes
+        int REPARENT_WITH_OVERLAY = 1;
+    }
+
+    @StyleableRes
+    static final int[] SLIDE = {
+            android.R.attr.slideEdge,
+    };
+
+    interface Slide {
+        @StyleableRes
+        int SLIDE_EDGE = 0;
+    }
+
+    @StyleableRes
+    static final int[] TRANSITION_SET = {
+            android.R.attr.transitionOrdering,
+    };
+
+    interface TransitionSet {
+        @StyleableRes
+        int TRANSITION_ORDERING = 0;
+    }
+
+    @StyleableRes
+    static final int[] ARC_MOTION = {
+            android.R.attr.minimumHorizontalAngle,
+            android.R.attr.minimumVerticalAngle,
+            android.R.attr.maximumAngle,
+    };
+
+    interface ArcMotion {
+        @StyleableRes
+        int MINIMUM_HORIZONTAL_ANGLE = 0;
+        @StyleableRes
+        int MINIMUM_VERTICAL_ANGLE = 1;
+        @StyleableRes
+        int MAXIMUM_ANGLE = 2;
+    }
+
+    @StyleableRes
+    static final int[] PATTERN_PATH_MOTION = {
+            android.R.attr.patternPathData,
+    };
+
+    interface PatternPathMotion {
+        @StyleableRes
+        int PATTERN_PATH_DATA = 0;
+    }
+
+    private Styleable() {
+    }
+}
diff --git a/androidx/transition/SyncRunnable.java b/androidx/transition/SyncRunnable.java
new file mode 100644
index 0000000..0cd391a
--- /dev/null
+++ b/androidx/transition/SyncRunnable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+class SyncRunnable implements Runnable {
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+
+    @Override
+    public void run() {
+        mLatch.countDown();
+    }
+
+    boolean await() {
+        try {
+            return mLatch.await(3000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+        }
+        return false;
+    }
+
+}
diff --git a/androidx/transition/SyncTransitionListener.java b/androidx/transition/SyncTransitionListener.java
new file mode 100644
index 0000000..204c05f
--- /dev/null
+++ b/androidx/transition/SyncTransitionListener.java
@@ -0,0 +1,87 @@
+/*
+ * 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 androidx.transition;
+
+import androidx.annotation.NonNull;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This {@link Transition.TransitionListener} synchronously waits for the specified callback.
+ */
+class SyncTransitionListener implements Transition.TransitionListener {
+
+    static final int EVENT_START = 1;
+    static final int EVENT_END = 2;
+    static final int EVENT_CANCEL = 3;
+    static final int EVENT_PAUSE = 4;
+    static final int EVENT_RESUME = 5;
+
+    private final int mTargetEvent;
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    SyncTransitionListener(int event) {
+        mTargetEvent = event;
+    }
+
+    boolean await() {
+        try {
+            return mLatch.await(3000, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
+    void reset() {
+        mLatch = new CountDownLatch(1);
+    }
+
+    @Override
+    public void onTransitionStart(@NonNull Transition transition) {
+        if (mTargetEvent == EVENT_START) {
+            mLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onTransitionEnd(@NonNull Transition transition) {
+        if (mTargetEvent == EVENT_END) {
+            mLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onTransitionCancel(@NonNull Transition transition) {
+        if (mTargetEvent == EVENT_CANCEL) {
+            mLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onTransitionPause(@NonNull Transition transition) {
+        if (mTargetEvent == EVENT_PAUSE) {
+            mLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onTransitionResume(@NonNull Transition transition) {
+        if (mTargetEvent == EVENT_RESUME) {
+            mLatch.countDown();
+        }
+    }
+}
diff --git a/androidx/transition/Transition.java b/androidx/transition/Transition.java
new file mode 100644
index 0000000..621da2f
--- /dev/null
+++ b/androidx/transition/Transition.java
@@ -0,0 +1,2438 @@
+/*
+ * 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.InflateException;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
+import android.widget.ListView;
+import android.widget.Spinner;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.collection.ArrayMap;
+import androidx.collection.LongSparseArray;
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.core.view.ViewCompat;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * A Transition holds information about animations that will be run on its
+ * targets during a scene change. Subclasses of this abstract class may
+ * choreograph several child transitions ({@link TransitionSet} or they may
+ * perform custom animations themselves. Any Transition has two main jobs:
+ * (1) capture property values, and (2) play animations based on changes to
+ * captured property values. A custom transition knows what property values
+ * on View objects are of interest to it, and also knows how to animate
+ * changes to those values. For example, the {@link Fade} transition tracks
+ * changes to visibility-related properties and is able to construct and run
+ * animations that fade items in or out based on changes to those properties.
+ *
+ * <p>Note: Transitions may not work correctly with either {@link SurfaceView}
+ * or {@link TextureView}, due to the way that these views are displayed
+ * on the screen. For SurfaceView, the problem is that the view is updated from
+ * a non-UI thread, so changes to the view due to transitions (such as moving
+ * and resizing the view) may be out of sync with the display inside those bounds.
+ * TextureView is more compatible with transitions in general, but some
+ * specific transitions (such as {@link Fade}) may not be compatible
+ * with TextureView because they rely on {@link android.view.ViewOverlay}
+ * functionality, which does not currently work with TextureView.</p>
+ *
+ * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
+ * directory. Transition resources consist of a tag name for one of the Transition
+ * subclasses along with attributes to define some of the attributes of that transition.
+ * For example, here is a minimal resource file that declares a {@link ChangeBounds}
+ * transition:</p>
+ *
+ * <pre>
+ *     &lt;changeBounds/&gt;
+ * </pre>
+ *
+ * <p>Note that attributes for the transition are not required, just as they are
+ * optional when declared in code; Transitions created from XML resources will use
+ * the same defaults as their code-created equivalents. Here is a slightly more
+ * elaborate example which declares a {@link TransitionSet} transition with
+ * {@link ChangeBounds} and {@link Fade} child transitions:</p>
+ *
+ * <pre>
+ *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ *          android:transitionOrdering="sequential"&gt;
+ *         &lt;changeBounds/&gt;
+ *         &lt;fade android:fadingMode="fade_out"&gt;
+ *             &lt;targets&gt;
+ *                 &lt;target android:targetId="@id/grayscaleContainer"/&gt;
+ *             &lt;/targets&gt;
+ *         &lt;/fade&gt;
+ *     &lt;/transitionSet&gt;
+ * </pre>
+ *
+ * <p>In this example, the transitionOrdering attribute is used on the TransitionSet
+ * object to change from the default {@link TransitionSet#ORDERING_TOGETHER} behavior
+ * to be {@link TransitionSet#ORDERING_SEQUENTIAL} instead. Also, the {@link Fade}
+ * transition uses a fadingMode of {@link Fade#OUT} instead of the default
+ * out-in behavior. Finally, note the use of the <code>targets</code> sub-tag, which
+ * takes a set of {code target} tags, each of which lists a specific <code>targetId</code> which
+ * this transition acts upon. Use of targets is optional, but can be used to either limit the time
+ * spent checking attributes on unchanging views, or limiting the types of animations run on
+ * specific views. In this case, we know that only the <code>grayscaleContainer</code> will be
+ * disappearing, so we choose to limit the {@link Fade} transition to only that view.</p>
+ */
+public abstract class Transition implements Cloneable {
+
+    private static final String LOG_TAG = "Transition";
+    static final boolean DBG = false;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by View instance.
+     */
+    public static final int MATCH_INSTANCE = 0x1;
+    private static final int MATCH_FIRST = MATCH_INSTANCE;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by
+     * {@link android.view.View#getTransitionName()}. Null names will not be matched.
+     */
+    public static final int MATCH_NAME = 0x2;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by
+     * {@link android.view.View#getId()}. Negative IDs will not be matched.
+     */
+    public static final int MATCH_ID = 0x3;
+
+    /**
+     * With {@link #setMatchOrder(int...)}, chooses to match by the {@link android.widget.Adapter}
+     * item id. When {@link android.widget.Adapter#hasStableIds()} returns false, no match
+     * will be made for items.
+     */
+    public static final int MATCH_ITEM_ID = 0x4;
+
+    private static final int MATCH_LAST = MATCH_ITEM_ID;
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef({MATCH_INSTANCE, MATCH_NAME, MATCH_ID, MATCH_ITEM_ID})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MatchOrder {
+    }
+
+    private static final String MATCH_INSTANCE_STR = "instance";
+    private static final String MATCH_NAME_STR = "name";
+    private static final String MATCH_ID_STR = "id";
+    private static final String MATCH_ITEM_ID_STR = "itemId";
+
+    private static final int[] DEFAULT_MATCH_ORDER = {
+            MATCH_NAME,
+            MATCH_INSTANCE,
+            MATCH_ID,
+            MATCH_ITEM_ID,
+    };
+
+    private static final PathMotion STRAIGHT_PATH_MOTION = new PathMotion() {
+        @Override
+        public Path getPath(float startX, float startY, float endX, float endY) {
+            Path path = new Path();
+            path.moveTo(startX, startY);
+            path.lineTo(endX, endY);
+            return path;
+        }
+    };
+
+    private String mName = getClass().getName();
+
+    private long mStartDelay = -1;
+    long mDuration = -1;
+    private TimeInterpolator mInterpolator = null;
+    ArrayList<Integer> mTargetIds = new ArrayList<>();
+    ArrayList<View> mTargets = new ArrayList<>();
+    private ArrayList<String> mTargetNames = null;
+    private ArrayList<Class> mTargetTypes = null;
+    private ArrayList<Integer> mTargetIdExcludes = null;
+    private ArrayList<View> mTargetExcludes = null;
+    private ArrayList<Class> mTargetTypeExcludes = null;
+    private ArrayList<String> mTargetNameExcludes = null;
+    private ArrayList<Integer> mTargetIdChildExcludes = null;
+    private ArrayList<View> mTargetChildExcludes = null;
+    private ArrayList<Class> mTargetTypeChildExcludes = null;
+    private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
+    private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
+    TransitionSet mParent = null;
+    private int[] mMatchOrder = DEFAULT_MATCH_ORDER;
+    private ArrayList<TransitionValues> mStartValuesList; // only valid after playTransition starts
+    private ArrayList<TransitionValues> mEndValuesList; // only valid after playTransitions starts
+
+    // Per-animator information used for later canceling when future transitions overlap
+    private static ThreadLocal<ArrayMap<Animator, Transition.AnimationInfo>> sRunningAnimators =
+            new ThreadLocal<>();
+
+    // Scene Root is set at createAnimator() time in the cloned Transition
+    private ViewGroup mSceneRoot = null;
+
+    // Whether removing views from their parent is possible. This is only for views
+    // in the start scene, which are no longer in the view hierarchy. This property
+    // is determined by whether the previous Scene was created from a layout
+    // resource, and thus the views from the exited scene are going away anyway
+    // and can be removed as necessary to achieve a particular effect, such as
+    // removing them from parents to add them to overlays.
+    boolean mCanRemoveViews = false;
+
+    // Track all animators in use in case the transition gets canceled and needs to
+    // cancel running animators
+    private ArrayList<Animator> mCurrentAnimators = new ArrayList<>();
+
+    // Number of per-target instances of this Transition currently running. This count is
+    // determined by calls to start() and end()
+    private int mNumInstances = 0;
+
+    // Whether this transition is currently paused, due to a call to pause()
+    private boolean mPaused = false;
+
+    // Whether this transition has ended. Used to avoid pause/resume on transitions
+    // that have completed
+    private boolean mEnded = false;
+
+    // The set of listeners to be sent transition lifecycle events.
+    private ArrayList<Transition.TransitionListener> mListeners = null;
+
+    // The set of animators collected from calls to createAnimator(),
+    // to be run in runAnimators()
+    private ArrayList<Animator> mAnimators = new ArrayList<>();
+
+    // The function for calculating the Animation start delay.
+    TransitionPropagation mPropagation;
+
+    // The rectangular region for Transitions like Explode and TransitionPropagations
+    // like CircularPropagation
+    private EpicenterCallback mEpicenterCallback;
+
+    // For Fragment shared element transitions, linking views explicitly by mismatching
+    // transitionNames.
+    private ArrayMap<String, String> mNameOverrides;
+
+    // The function used to interpolate along two-dimensional points. Typically used
+    // for adding curves to x/y View motion.
+    private PathMotion mPathMotion = STRAIGHT_PATH_MOTION;
+
+    /**
+     * Constructs a Transition object with no target objects. A transition with
+     * no targets defaults to running on all target objects in the scene hierarchy
+     * (if the transition is not contained in a TransitionSet), or all target
+     * objects passed down from its parent (if it is in a TransitionSet).
+     */
+    public Transition() {
+    }
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style from a
+     * theme attribute or style resource. This constructor of Transition allows
+     * subclasses to use their own base style when they are inflating.
+     *
+     * @param context The Context the transition is running in, through which it can
+     *                access the current theme, resources, etc.
+     * @param attrs   The attributes of the XML tag that is inflating the transition.
+     */
+    public Transition(Context context, AttributeSet attrs) {
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION);
+        XmlResourceParser parser = (XmlResourceParser) attrs;
+        long duration = TypedArrayUtils.getNamedInt(a, parser, "duration",
+                Styleable.Transition.DURATION, -1);
+        if (duration >= 0) {
+            setDuration(duration);
+        }
+        long startDelay = TypedArrayUtils.getNamedInt(a, parser, "startDelay",
+                Styleable.Transition.START_DELAY, -1);
+        if (startDelay > 0) {
+            setStartDelay(startDelay);
+        }
+        final int resId = TypedArrayUtils.getNamedResourceId(a, parser, "interpolator",
+                Styleable.Transition.INTERPOLATOR, 0);
+        if (resId > 0) {
+            setInterpolator(AnimationUtils.loadInterpolator(context, resId));
+        }
+        String matchOrder = TypedArrayUtils.getNamedString(a, parser, "matchOrder",
+                Styleable.Transition.MATCH_ORDER);
+        if (matchOrder != null) {
+            setMatchOrder(parseMatchOrder(matchOrder));
+        }
+        a.recycle();
+    }
+
+    @MatchOrder
+    private static int[] parseMatchOrder(String matchOrderString) {
+        StringTokenizer st = new StringTokenizer(matchOrderString, ",");
+        @MatchOrder
+        int[] matches = new int[st.countTokens()];
+        int index = 0;
+        while (st.hasMoreTokens()) {
+            String token = st.nextToken().trim();
+            if (MATCH_ID_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_ID;
+            } else if (MATCH_INSTANCE_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_INSTANCE;
+            } else if (MATCH_NAME_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_NAME;
+            } else if (MATCH_ITEM_ID_STR.equalsIgnoreCase(token)) {
+                matches[index] = Transition.MATCH_ITEM_ID;
+            } else if (token.isEmpty()) {
+                @MatchOrder
+                int[] smallerMatches = new int[matches.length - 1];
+                System.arraycopy(matches, 0, smallerMatches, 0, index);
+                matches = smallerMatches;
+                index--;
+            } else {
+                throw new InflateException("Unknown match type in matchOrder: '" + token + "'");
+            }
+            index++;
+        }
+        return matches;
+    }
+
+    /**
+     * Sets the duration of this transition. By default, there is no duration
+     * (indicated by a negative number), which means that the Animator created by
+     * the transition will have its own specified duration. If the duration of a
+     * Transition is set, that duration will override the Animator duration.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setDuration(long duration) {
+        mDuration = duration;
+        return this;
+    }
+
+    /**
+     * Returns the duration set on this transition. If no duration has been set,
+     * the returned value will be negative, indicating that resulting animators will
+     * retain their own durations.
+     *
+     * @return The duration set on this transition, in milliseconds, if one has been
+     * set, otherwise returns a negative number.
+     */
+    public long getDuration() {
+        return mDuration;
+    }
+
+    /**
+     * Sets the startDelay of this transition. By default, there is no delay
+     * (indicated by a negative number), which means that the Animator created by
+     * the transition will have its own specified startDelay. If the delay of a
+     * Transition is set, that delay will override the Animator delay.
+     *
+     * @param startDelay The length of the delay, in milliseconds.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setStartDelay(long startDelay) {
+        mStartDelay = startDelay;
+        return this;
+    }
+
+    /**
+     * Returns the startDelay set on this transition. If no startDelay has been set,
+     * the returned value will be negative, indicating that resulting animators will
+     * retain their own startDelays.
+     *
+     * @return The startDelay set on this transition, in milliseconds, if one has
+     * been set, otherwise returns a negative number.
+     */
+    public long getStartDelay() {
+        return mStartDelay;
+    }
+
+    /**
+     * Sets the interpolator of this transition. By default, the interpolator
+     * is null, which means that the Animator created by the transition
+     * will have its own specified interpolator. If the interpolator of a
+     * Transition is set, that interpolator will override the Animator interpolator.
+     *
+     * @param interpolator The time interpolator used by the transition
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition setInterpolator(@Nullable TimeInterpolator interpolator) {
+        mInterpolator = interpolator;
+        return this;
+    }
+
+    /**
+     * Returns the interpolator set on this transition. If no interpolator has been set,
+     * the returned value will be null, indicating that resulting animators will
+     * retain their own interpolators.
+     *
+     * @return The interpolator set on this transition, if one has been set, otherwise
+     * returns null.
+     */
+    @Nullable
+    public TimeInterpolator getInterpolator() {
+        return mInterpolator;
+    }
+
+    /**
+     * Returns the set of property names used stored in the {@link TransitionValues}
+     * object passed into {@link #captureStartValues(TransitionValues)} that
+     * this transition cares about for the purposes of canceling overlapping animations.
+     * When any transition is started on a given scene root, all transitions
+     * currently running on that same scene root are checked to see whether the
+     * properties on which they based their animations agree with the end values of
+     * the same properties in the new transition. If the end values are not equal,
+     * then the old animation is canceled since the new transition will start a new
+     * animation to these new values. If the values are equal, the old animation is
+     * allowed to continue and no new animation is started for that transition.
+     *
+     * <p>A transition does not need to override this method. However, not doing so
+     * will mean that the cancellation logic outlined in the previous paragraph
+     * will be skipped for that transition, possibly leading to artifacts as
+     * old transitions and new transitions on the same targets run in parallel,
+     * animating views toward potentially different end values.</p>
+     *
+     * @return An array of property names as described in the class documentation for
+     * {@link TransitionValues}. The default implementation returns <code>null</code>.
+     */
+    @Nullable
+    public String[] getTransitionProperties() {
+        return null;
+    }
+
+    /**
+     * This method creates an animation that will be run for this transition
+     * given the information in the startValues and endValues structures captured
+     * earlier for the start and end scenes. Subclasses of Transition should override
+     * this method. The method should only be called by the transition system; it is
+     * not intended to be called from external classes.
+     *
+     * <p>This method is called by the transition's parent (all the way up to the
+     * topmost Transition in the hierarchy) with the sceneRoot and start/end
+     * values that the transition may need to set up initial target values
+     * and construct an appropriate animation. For example, if an overall
+     * Transition is a {@link TransitionSet} consisting of several
+     * child transitions in sequence, then some of the child transitions may
+     * want to set initial values on target views prior to the overall
+     * Transition commencing, to put them in an appropriate state for the
+     * delay between that start and the child Transition start time. For
+     * example, a transition that fades an item in may wish to set the starting
+     * alpha value to 0, to avoid it blinking in prior to the transition
+     * actually starting the animation. This is necessary because the scene
+     * change that triggers the Transition will automatically set the end-scene
+     * on all target views, so a Transition that wants to animate from a
+     * different value should set that value prior to returning from this method.</p>
+     *
+     * <p>Additionally, a Transition can perform logic to determine whether
+     * the transition needs to run on the given target and start/end values.
+     * For example, a transition that resizes objects on the screen may wish
+     * to avoid running for views which are not present in either the start
+     * or end scenes.</p>
+     *
+     * <p>If there is an animator created and returned from this method, the
+     * transition mechanism will apply any applicable duration, startDelay,
+     * and interpolator to that animation and start it. A return value of
+     * <code>null</code> indicates that no animation should run. The default
+     * implementation returns null.</p>
+     *
+     * <p>The method is called for every applicable target object, which is
+     * stored in the {@link TransitionValues#view} field.</p>
+     *
+     * @param sceneRoot   The root of the transition hierarchy.
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return A Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    @Nullable
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+        return null;
+    }
+
+    /**
+     * Sets the order in which Transition matches View start and end values.
+     * <p>
+     * The default behavior is to match first by {@link android.view.View#getTransitionName()},
+     * then by View instance, then by {@link android.view.View#getId()} and finally
+     * by its item ID if it is in a direct child of ListView. The caller can
+     * choose to have only some or all of the values of {@link #MATCH_INSTANCE},
+     * {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}. Only
+     * the match algorithms supplied will be used to determine whether Views are the
+     * the same in both the start and end Scene. Views that do not match will be considered
+     * as entering or leaving the Scene.
+     * </p>
+     *
+     * @param matches A list of zero or more of {@link #MATCH_INSTANCE},
+     *                {@link #MATCH_NAME}, {@link #MATCH_ITEM_ID}, and {@link #MATCH_ID}.
+     *                If none are provided, then the default match order will be set.
+     */
+    public void setMatchOrder(@MatchOrder int... matches) {
+        if (matches == null || matches.length == 0) {
+            mMatchOrder = DEFAULT_MATCH_ORDER;
+        } else {
+            for (int i = 0; i < matches.length; i++) {
+                int match = matches[i];
+                if (!isValidMatch(match)) {
+                    throw new IllegalArgumentException("matches contains invalid value");
+                }
+                if (alreadyContains(matches, i)) {
+                    throw new IllegalArgumentException("matches contains a duplicate value");
+                }
+            }
+            mMatchOrder = matches.clone();
+        }
+    }
+
+    private static boolean isValidMatch(int match) {
+        return (match >= MATCH_FIRST && match <= MATCH_LAST);
+    }
+
+    private static boolean alreadyContains(int[] array, int searchIndex) {
+        int value = array[searchIndex];
+        for (int i = 0; i < searchIndex; i++) {
+            if (array[i] == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Match start/end values by View instance. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd.
+     */
+    private void matchInstances(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd) {
+        for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
+            View view = unmatchedStart.keyAt(i);
+            if (view != null && isValidTarget(view)) {
+                TransitionValues end = unmatchedEnd.remove(view);
+                if (end != null && end.view != null && isValidTarget(end.view)) {
+                    TransitionValues start = unmatchedStart.removeAt(i);
+                    mStartValuesList.add(start);
+                    mEndValuesList.add(end);
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter item ID. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startItemIds and endItemIds as a guide for which Views have unique item IDs.
+     */
+    private void matchItemIds(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            LongSparseArray<View> startItemIds, LongSparseArray<View> endItemIds) {
+        int numStartIds = startItemIds.size();
+        for (int i = 0; i < numStartIds; i++) {
+            View startView = startItemIds.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endItemIds.get(startItemIds.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter view ID. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startIds and endIds as a guide for which Views have unique IDs.
+     */
+    private void matchIds(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            SparseArray<View> startIds, SparseArray<View> endIds) {
+        int numStartIds = startIds.size();
+        for (int i = 0; i < numStartIds; i++) {
+            View startView = startIds.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endIds.get(startIds.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Match start/end values by Adapter transitionName. Adds matched values to mStartValuesList
+     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
+     * startNames and endNames as a guide for which Views have unique transitionNames.
+     */
+    private void matchNames(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd,
+            ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
+        int numStartNames = startNames.size();
+        for (int i = 0; i < numStartNames; i++) {
+            View startView = startNames.valueAt(i);
+            if (startView != null && isValidTarget(startView)) {
+                View endView = endNames.get(startNames.keyAt(i));
+                if (endView != null && isValidTarget(endView)) {
+                    TransitionValues startValues = unmatchedStart.get(startView);
+                    TransitionValues endValues = unmatchedEnd.get(endView);
+                    if (startValues != null && endValues != null) {
+                        mStartValuesList.add(startValues);
+                        mEndValuesList.add(endValues);
+                        unmatchedStart.remove(startView);
+                        unmatchedEnd.remove(endView);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds all values from unmatchedStart and unmatchedEnd to mStartValuesList and mEndValuesList,
+     * assuming that there is no match between values in the list.
+     */
+    private void addUnmatched(ArrayMap<View, TransitionValues> unmatchedStart,
+            ArrayMap<View, TransitionValues> unmatchedEnd) {
+        // Views that only exist in the start Scene
+        for (int i = 0; i < unmatchedStart.size(); i++) {
+            final TransitionValues start = unmatchedStart.valueAt(i);
+            if (isValidTarget(start.view)) {
+                mStartValuesList.add(start);
+                mEndValuesList.add(null);
+            }
+        }
+
+        // Views that only exist in the end Scene
+        for (int i = 0; i < unmatchedEnd.size(); i++) {
+            final TransitionValues end = unmatchedEnd.valueAt(i);
+            if (isValidTarget(end.view)) {
+                mEndValuesList.add(end);
+                mStartValuesList.add(null);
+            }
+        }
+    }
+
+    private void matchStartAndEnd(TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues) {
+        ArrayMap<View, TransitionValues> unmatchedStart = new ArrayMap<>(startValues.mViewValues);
+        ArrayMap<View, TransitionValues> unmatchedEnd = new ArrayMap<>(endValues.mViewValues);
+
+        for (int i = 0; i < mMatchOrder.length; i++) {
+            switch (mMatchOrder[i]) {
+                case MATCH_INSTANCE:
+                    matchInstances(unmatchedStart, unmatchedEnd);
+                    break;
+                case MATCH_NAME:
+                    matchNames(unmatchedStart, unmatchedEnd,
+                            startValues.mNameValues, endValues.mNameValues);
+                    break;
+                case MATCH_ID:
+                    matchIds(unmatchedStart, unmatchedEnd,
+                            startValues.mIdValues, endValues.mIdValues);
+                    break;
+                case MATCH_ITEM_ID:
+                    matchItemIds(unmatchedStart, unmatchedEnd,
+                            startValues.mItemIdValues, endValues.mItemIdValues);
+                    break;
+            }
+        }
+        addUnmatched(unmatchedStart, unmatchedEnd);
+    }
+
+    /**
+     * This method, essentially a wrapper around all calls to createAnimator for all
+     * possible target views, is called with the entire set of start/end
+     * values. The implementation in Transition iterates through these lists
+     * and calls {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * with each set of start/end values on this transition. The
+     * TransitionSet subclass overrides this method and delegates it to
+     * each of its children in succession.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
+            ArrayList<TransitionValues> endValuesList) {
+        if (DBG) {
+            Log.d(LOG_TAG, "createAnimators() for " + this);
+        }
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        long minStartDelay = Long.MAX_VALUE;
+        SparseIntArray startDelays = new SparseIntArray();
+        int startValuesListCount = startValuesList.size();
+        for (int i = 0; i < startValuesListCount; ++i) {
+            TransitionValues start = startValuesList.get(i);
+            TransitionValues end = endValuesList.get(i);
+            if (start != null && !start.mTargetedTransitions.contains(this)) {
+                start = null;
+            }
+            if (end != null && !end.mTargetedTransitions.contains(this)) {
+                end = null;
+            }
+            if (start == null && end == null) {
+                continue;
+            }
+            // Only bother trying to animate with values that differ between start/end
+            boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
+            if (isChanged) {
+                if (DBG) {
+                    View view = (end != null) ? end.view : start.view;
+                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
+                    if (start == null || end == null) {
+                        Log.d(LOG_TAG, "    " + ((start == null)
+                                ? "start null, end non-null" : "start non-null, end null"));
+                    } else {
+                        for (String key : start.values.keySet()) {
+                            Object startValue = start.values.get(key);
+                            Object endValue = end.values.get(key);
+                            if (startValue != endValue && !startValue.equals(endValue)) {
+                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue
+                                        + "), end(" + endValue + ")");
+                            }
+                        }
+                    }
+                }
+                // TODO: what to do about targetIds and itemIds?
+                Animator animator = createAnimator(sceneRoot, start, end);
+                if (animator != null) {
+                    // Save animation info for future cancellation purposes
+                    View view;
+                    TransitionValues infoValues = null;
+                    if (end != null) {
+                        view = end.view;
+                        String[] properties = getTransitionProperties();
+                        if (view != null && properties != null && properties.length > 0) {
+                            infoValues = new TransitionValues();
+                            infoValues.view = view;
+                            TransitionValues newValues = endValues.mViewValues.get(view);
+                            if (newValues != null) {
+                                for (int j = 0; j < properties.length; ++j) {
+                                    infoValues.values.put(properties[j],
+                                            newValues.values.get(properties[j]));
+                                }
+                            }
+                            int numExistingAnims = runningAnimators.size();
+                            for (int j = 0; j < numExistingAnims; ++j) {
+                                Animator anim = runningAnimators.keyAt(j);
+                                AnimationInfo info = runningAnimators.get(anim);
+                                if (info.mValues != null && info.mView == view
+                                        && info.mName.equals(getName())) {
+                                    if (info.mValues.equals(infoValues)) {
+                                        // Favor the old animator
+                                        animator = null;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                    } else {
+                        view = start.view;
+                    }
+                    if (animator != null) {
+                        if (mPropagation != null) {
+                            long delay = mPropagation.getStartDelay(sceneRoot, this, start, end);
+                            startDelays.put(mAnimators.size(), (int) delay);
+                            minStartDelay = Math.min(delay, minStartDelay);
+                        }
+                        AnimationInfo info = new AnimationInfo(view, getName(), this,
+                                ViewUtils.getWindowId(sceneRoot), infoValues);
+                        runningAnimators.put(animator, info);
+                        mAnimators.add(animator);
+                    }
+                }
+            }
+        }
+        if (minStartDelay != 0) {
+            for (int i = 0; i < startDelays.size(); i++) {
+                int index = startDelays.keyAt(i);
+                Animator animator = mAnimators.get(index);
+                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+                animator.setStartDelay(delay);
+            }
+        }
+    }
+
+    /**
+     * Internal utility method for checking whether a given view/id
+     * is valid for this transition, where "valid" means that either
+     * the Transition has no target/targetId list (the default, in which
+     * cause the transition should act on all views in the hiearchy), or
+     * the given view is in the target list or the view id is in the
+     * targetId list. If the target parameter is null, then the target list
+     * is not checked (this is in the case of ListView items, where the
+     * views are ignored and only the ids are used).
+     */
+    boolean isValidTarget(View target) {
+        int targetId = target.getId();
+        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(targetId)) {
+            return false;
+        }
+        if (mTargetExcludes != null && mTargetExcludes.contains(target)) {
+            return false;
+        }
+        if (mTargetTypeExcludes != null) {
+            int numTypes = mTargetTypeExcludes.size();
+            for (int i = 0; i < numTypes; ++i) {
+                Class type = mTargetTypeExcludes.get(i);
+                if (type.isInstance(target)) {
+                    return false;
+                }
+            }
+        }
+        if (mTargetNameExcludes != null && ViewCompat.getTransitionName(target) != null) {
+            if (mTargetNameExcludes.contains(ViewCompat.getTransitionName(target))) {
+                return false;
+            }
+        }
+        if (mTargetIds.size() == 0 && mTargets.size() == 0
+                && (mTargetTypes == null || mTargetTypes.isEmpty())
+                && (mTargetNames == null || mTargetNames.isEmpty())) {
+            return true;
+        }
+        if (mTargetIds.contains(targetId) || mTargets.contains(target)) {
+            return true;
+        }
+        if (mTargetNames != null && mTargetNames.contains(ViewCompat.getTransitionName(target))) {
+            return true;
+        }
+        if (mTargetTypes != null) {
+            for (int i = 0; i < mTargetTypes.size(); ++i) {
+                if (mTargetTypes.get(i).isInstance(target)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static ArrayMap<Animator, AnimationInfo> getRunningAnimators() {
+        ArrayMap<Animator, AnimationInfo> runningAnimators = sRunningAnimators.get();
+        if (runningAnimators == null) {
+            runningAnimators = new ArrayMap<>();
+            sRunningAnimators.set(runningAnimators);
+        }
+        return runningAnimators;
+    }
+
+    /**
+     * This is called internally once all animations have been set up by the
+     * transition hierarchy. \
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void runAnimators() {
+        if (DBG) {
+            Log.d(LOG_TAG, "runAnimators() on " + this);
+        }
+        start();
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        // Now start every Animator that was previously created for this transition
+        for (Animator anim : mAnimators) {
+            if (DBG) {
+                Log.d(LOG_TAG, "  anim: " + anim);
+            }
+            if (runningAnimators.containsKey(anim)) {
+                start();
+                runAnimator(anim, runningAnimators);
+            }
+        }
+        mAnimators.clear();
+        end();
+    }
+
+    private void runAnimator(Animator animator,
+            final ArrayMap<Animator, AnimationInfo> runningAnimators) {
+        if (animator != null) {
+            // TODO: could be a single listener instance for all of them since it uses the param
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    mCurrentAnimators.add(animation);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    runningAnimators.remove(animation);
+                    mCurrentAnimators.remove(animation);
+                }
+            });
+            animate(animator);
+        }
+    }
+
+    /**
+     * Captures the values in the start scene for the properties that this
+     * transition monitors. These values are then passed as the startValues
+     * structure in a later call to
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * method to determine what, if any, animations, should be run.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     * @see #captureEndValues(TransitionValues)
+     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
+     */
+    public abstract void captureStartValues(@NonNull TransitionValues transitionValues);
+
+    /**
+     * Captures the values in the end scene for the properties that this
+     * transition monitors. These values are then passed as the endValues
+     * structure in a later call to
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * method to determine what, if any, animations, should be run.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     * @see #captureStartValues(TransitionValues)
+     * @see #createAnimator(ViewGroup, TransitionValues, TransitionValues)
+     */
+    public abstract void captureEndValues(@NonNull TransitionValues transitionValues);
+
+    /**
+     * Sets the target view instances that this Transition is interested in
+     * animating. By default, there are no targets, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targets constrains
+     * the Transition to only listen for, and act on, these views.
+     * All other views will be ignored.
+     *
+     * <p>The target list is like the {@link #addTarget(int) targetId}
+     * list except this list specifies the actual View instances, not the ids
+     * of the views. This is an important distinction when scene changes involve
+     * view hierarchies which have been inflated separately; different views may
+     * share the same id but not actually be the same instance. If the transition
+     * should treat those views as the same, then {@link #addTarget(int)} should be used
+     * instead of {@link #addTarget(View)}. If, on the other hand, scene changes involve
+     * changes all within the same view hierarchy, among views which do not
+     * necessarily have ids set on them, then the target list of views may be more
+     * convenient.</p>
+     *
+     * @param target A View on which the Transition will act, must be non-null.
+     * @return The Transition to which the target is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someView);</code>
+     * @see #addTarget(int)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull View target) {
+        mTargets.add(target);
+        return this;
+    }
+
+    /**
+     * Adds the id of a target view that this Transition is interested in
+     * animating. By default, there are no targetIds, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetIds constrains
+     * the Transition to only listen for, and act on, views with these IDs.
+     * Views with different IDs, or no IDs whatsoever, will be ignored.
+     *
+     * <p>Note that using ids to specify targets implies that ids should be unique
+     * within the view hierarchy underneath the scene root.</p>
+     *
+     * @param targetId The id of a target view, must be a positive number.
+     * @return The Transition to which the targetId is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someId);</code>
+     * @see View#getId()
+     */
+    @NonNull
+    public Transition addTarget(@IdRes int targetId) {
+        if (targetId != 0) {
+            mTargetIds.add(targetId);
+        }
+        return this;
+    }
+
+    /**
+     * Adds the transitionName of a target view that this Transition is interested in
+     * animating. By default, there are no targetNames, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetNames constrains
+     * the Transition to only listen for, and act on, views with these transitionNames.
+     * Views with different transitionNames, or no transitionName whatsoever, will be ignored.
+     *
+     * <p>Note that transitionNames should be unique within the view hierarchy.</p>
+     *
+     * @param targetName The transitionName of a target view, must be non-null.
+     * @return The Transition to which the target transitionName is added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(someName);</code>
+     * @see ViewCompat#getTransitionName(View)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull String targetName) {
+        if (mTargetNames == null) {
+            mTargetNames = new ArrayList<>();
+        }
+        mTargetNames.add(targetName);
+        return this;
+    }
+
+    /**
+     * Adds the Class of a target view that this Transition is interested in
+     * animating. By default, there are no targetTypes, and a Transition will
+     * listen for changes on every view in the hierarchy below the sceneRoot
+     * of the Scene being transitioned into. Setting targetTypes constrains
+     * the Transition to only listen for, and act on, views with these classes.
+     * Views with different classes will be ignored.
+     *
+     * <p>Note that any View that can be cast to targetType will be included, so
+     * if targetType is <code>View.class</code>, all Views will be included.</p>
+     *
+     * @param targetType The type to include when running this transition.
+     * @return The Transition to which the target class was added.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).addTarget(ImageView.class);</code>
+     * @see #addTarget(int)
+     * @see #addTarget(android.view.View)
+     * @see #excludeTarget(Class, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition addTarget(@NonNull Class targetType) {
+        if (mTargetTypes == null) {
+            mTargetTypes = new ArrayList<>();
+        }
+        mTargetTypes.add(targetType);
+        return this;
+    }
+
+    /**
+     * Removes the given target from the list of targets that this Transition
+     * is interested in animating.
+     *
+     * @param target The target view, must be non-null.
+     * @return Transition The Transition from which the target is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someView);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull View target) {
+        mTargets.remove(target);
+        return this;
+    }
+
+    /**
+     * Removes the given targetId from the list of ids that this Transition
+     * is interested in animating.
+     *
+     * @param targetId The id of a target view, must be a positive number.
+     * @return The Transition from which the targetId is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTargetId(someId);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@IdRes int targetId) {
+        if (targetId != 0) {
+            mTargetIds.remove((Integer) targetId);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the given targetName from the list of transitionNames that this Transition
+     * is interested in animating.
+     *
+     * @param targetName The transitionName of a target view, must not be null.
+     * @return The Transition from which the targetName is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTargetName(someName);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull String targetName) {
+        if (mTargetNames != null) {
+            mTargetNames.remove(targetName);
+        }
+        return this;
+    }
+
+    /**
+     * Removes the given target from the list of targets that this Transition
+     * is interested in animating.
+     *
+     * @param target The type of the target view, must be non-null.
+     * @return Transition The Transition from which the target is removed.
+     * Returning the same object makes it easier to chain calls during
+     * construction, such as
+     * <code>transitionSet.addTransitions(new Fade()).removeTarget(someType);</code>
+     */
+    @NonNull
+    public Transition removeTarget(@NonNull Class target) {
+        if (mTargetTypes != null) {
+            mTargetTypes.remove(target);
+        }
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private static <T> ArrayList<T> excludeObject(ArrayList<T> list, T target, boolean exclude) {
+        if (target != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, target);
+            } else {
+                list = ArrayListManager.remove(list, target);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Whether to add the given target to the list of targets to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param target  The target to ignore when running this transition.
+     * @param exclude Whether to add the target to or remove the target from the
+     *                current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeChildren(View, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull View target, boolean exclude) {
+        mTargetExcludes = excludeView(mTargetExcludes, target, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given id to the list of target ids to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetId The id of a target to ignore when running this transition.
+     * @param exclude  Whether to add the target to or remove the target from the
+     *                 current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@IdRes int targetId, boolean exclude) {
+        mTargetIdExcludes = excludeId(mTargetIdExcludes, targetId, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given transitionName to the list of target transitionNames to exclude
+     * from this transition. The <code>exclude</code> parameter specifies whether the target
+     * should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded by their
+     * id, their instance reference, their transitionName, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetName The name of a target to ignore when running this transition.
+     * @param exclude    Whether to add the target to or remove the target from the
+     *                   current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull String targetName, boolean exclude) {
+        mTargetNameExcludes = excludeObject(mTargetNameExcludes, targetName, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the children of given target to the list of target children
+     * to exclude from this transition. The <code>exclude</code> parameter specifies
+     * whether the target should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param target  The target to ignore when running this transition.
+     * @param exclude Whether to add the target to or remove the target from the
+     *                current list of excluded targets.
+     * @return This transition object.
+     * @see #excludeTarget(View, boolean)
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@NonNull View target, boolean exclude) {
+        mTargetChildExcludes = excludeView(mTargetChildExcludes, target, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the children of the given id to the list of targets to exclude
+     * from this transition. The <code>exclude</code> parameter specifies whether
+     * the children of the target should be added to or removed from the excluded list.
+     * Excluding children in this way provides a simple mechanism for excluding all
+     * children of specific targets, rather than individually excluding each
+     * child individually.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param targetId The id of a target whose children should be ignored when running
+     *                 this transition.
+     * @param exclude  Whether to add the target to or remove the target from the
+     *                 current list of excluded-child targets.
+     * @return This transition object.
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeChildren(View, boolean)
+     * @see #excludeChildren(Class, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@IdRes int targetId, boolean exclude) {
+        mTargetIdChildExcludes = excludeId(mTargetIdChildExcludes, targetId, exclude);
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<Integer> excludeId(ArrayList<Integer> list, int targetId, boolean exclude) {
+        if (targetId > 0) {
+            if (exclude) {
+                list = ArrayListManager.add(list, targetId);
+            } else {
+                list = ArrayListManager.remove(list, targetId);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<View> excludeView(ArrayList<View> list, View target, boolean exclude) {
+        if (target != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, target);
+            } else {
+                list = ArrayListManager.remove(list, target);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Whether to add the given type to the list of types to exclude from this
+     * transition. The <code>exclude</code> parameter specifies whether the target
+     * type should be added to or removed from the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param type    The type to ignore when running this transition.
+     * @param exclude Whether to add the target type to or remove it from the
+     *                current list of excluded target types.
+     * @return This transition object.
+     * @see #excludeChildren(Class, boolean)
+     * @see #excludeTarget(int, boolean)
+     * @see #excludeTarget(View, boolean)
+     */
+    @NonNull
+    public Transition excludeTarget(@NonNull Class type, boolean exclude) {
+        mTargetTypeExcludes = excludeType(mTargetTypeExcludes, type, exclude);
+        return this;
+    }
+
+    /**
+     * Whether to add the given type to the list of types whose children should
+     * be excluded from this transition. The <code>exclude</code> parameter
+     * specifies whether the target type should be added to or removed from
+     * the excluded list.
+     *
+     * <p>Excluding targets is a general mechanism for allowing transitions to run on
+     * a view hierarchy while skipping target views that should not be part of
+     * the transition. For example, you may want to avoid animating children
+     * of a specific ListView or Spinner. Views can be excluded either by their
+     * id, or by their instance reference, or by the Class of that view
+     * (eg, {@link Spinner}).</p>
+     *
+     * @param type    The type to ignore when running this transition.
+     * @param exclude Whether to add the target type to or remove it from the
+     *                current list of excluded target types.
+     * @return This transition object.
+     * @see #excludeTarget(Class, boolean)
+     * @see #excludeChildren(int, boolean)
+     * @see #excludeChildren(View, boolean)
+     */
+    @NonNull
+    public Transition excludeChildren(@NonNull Class type, boolean exclude) {
+        mTargetTypeChildExcludes = excludeType(mTargetTypeChildExcludes, type, exclude);
+        return this;
+    }
+
+    /**
+     * Utility method to manage the boilerplate code that is the same whether we
+     * are excluding targets or their children.
+     */
+    private ArrayList<Class> excludeType(ArrayList<Class> list, Class type, boolean exclude) {
+        if (type != null) {
+            if (exclude) {
+                list = ArrayListManager.add(list, type);
+            } else {
+                list = ArrayListManager.remove(list, type);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Returns the array of target IDs that this transition limits itself to
+     * tracking and animating. If the array is null for both this method and
+     * {@link #getTargets()}, then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target IDs
+     */
+    @NonNull
+    public List<Integer> getTargetIds() {
+        return mTargetIds;
+    }
+
+    /**
+     * Returns the array of target views that this transition limits itself to
+     * tracking and animating. If the array is null for both this method and
+     * {@link #getTargetIds()}, then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target views
+     */
+    @NonNull
+    public List<View> getTargets() {
+        return mTargets;
+    }
+
+    /**
+     * Returns the list of target transitionNames that this transition limits itself to
+     * tracking and animating. If the list is null or empty for
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
+     * {@link #getTargetTypes()} then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target transitionNames
+     */
+    @Nullable
+    public List<String> getTargetNames() {
+        return mTargetNames;
+    }
+
+    /**
+     * Returns the list of target transitionNames that this transition limits itself to
+     * tracking and animating. If the list is null or empty for
+     * {@link #getTargetIds()}, {@link #getTargets()}, {@link #getTargetNames()}, and
+     * {@link #getTargetTypes()} then this transition is
+     * not limited to specific views, and will handle changes to any views
+     * in the hierarchy of a scene change.
+     *
+     * @return the list of target Types
+     */
+    @Nullable
+    public List<Class> getTargetTypes() {
+        return mTargetTypes;
+    }
+
+    /**
+     * Recursive method that captures values for the given view and the
+     * hierarchy underneath it.
+     *
+     * @param sceneRoot The root of the view hierarchy being captured
+     * @param start     true if this capture is happening before the scene change,
+     *                  false otherwise
+     */
+    void captureValues(ViewGroup sceneRoot, boolean start) {
+        clearValues(start);
+        if ((mTargetIds.size() > 0 || mTargets.size() > 0)
+                && (mTargetNames == null || mTargetNames.isEmpty())
+                && (mTargetTypes == null || mTargetTypes.isEmpty())) {
+            for (int i = 0; i < mTargetIds.size(); ++i) {
+                int id = mTargetIds.get(i);
+                View view = sceneRoot.findViewById(id);
+                if (view != null) {
+                    TransitionValues values = new TransitionValues();
+                    values.view = view;
+                    if (start) {
+                        captureStartValues(values);
+                    } else {
+                        captureEndValues(values);
+                    }
+                    values.mTargetedTransitions.add(this);
+                    capturePropagationValues(values);
+                    if (start) {
+                        addViewValues(mStartValues, view, values);
+                    } else {
+                        addViewValues(mEndValues, view, values);
+                    }
+                }
+            }
+            for (int i = 0; i < mTargets.size(); ++i) {
+                View view = mTargets.get(i);
+                TransitionValues values = new TransitionValues();
+                values.view = view;
+                if (start) {
+                    captureStartValues(values);
+                } else {
+                    captureEndValues(values);
+                }
+                values.mTargetedTransitions.add(this);
+                capturePropagationValues(values);
+                if (start) {
+                    addViewValues(mStartValues, view, values);
+                } else {
+                    addViewValues(mEndValues, view, values);
+                }
+            }
+        } else {
+            captureHierarchy(sceneRoot, start);
+        }
+        if (!start && mNameOverrides != null) {
+            int numOverrides = mNameOverrides.size();
+            ArrayList<View> overriddenViews = new ArrayList<>(numOverrides);
+            for (int i = 0; i < numOverrides; i++) {
+                String fromName = mNameOverrides.keyAt(i);
+                overriddenViews.add(mStartValues.mNameValues.remove(fromName));
+            }
+            for (int i = 0; i < numOverrides; i++) {
+                View view = overriddenViews.get(i);
+                if (view != null) {
+                    String toName = mNameOverrides.valueAt(i);
+                    mStartValues.mNameValues.put(toName, view);
+                }
+            }
+        }
+    }
+
+    private static void addViewValues(TransitionValuesMaps transitionValuesMaps,
+            View view, TransitionValues transitionValues) {
+        transitionValuesMaps.mViewValues.put(view, transitionValues);
+        int id = view.getId();
+        if (id >= 0) {
+            if (transitionValuesMaps.mIdValues.indexOfKey(id) >= 0) {
+                // Duplicate IDs cannot match by ID.
+                transitionValuesMaps.mIdValues.put(id, null);
+            } else {
+                transitionValuesMaps.mIdValues.put(id, view);
+            }
+        }
+        String name = ViewCompat.getTransitionName(view);
+        if (name != null) {
+            if (transitionValuesMaps.mNameValues.containsKey(name)) {
+                // Duplicate transitionNames: cannot match by transitionName.
+                transitionValuesMaps.mNameValues.put(name, null);
+            } else {
+                transitionValuesMaps.mNameValues.put(name, view);
+            }
+        }
+        if (view.getParent() instanceof ListView) {
+            ListView listview = (ListView) view.getParent();
+            if (listview.getAdapter().hasStableIds()) {
+                int position = listview.getPositionForView(view);
+                long itemId = listview.getItemIdAtPosition(position);
+                if (transitionValuesMaps.mItemIdValues.indexOfKey(itemId) >= 0) {
+                    // Duplicate item IDs: cannot match by item ID.
+                    View alreadyMatched = transitionValuesMaps.mItemIdValues.get(itemId);
+                    if (alreadyMatched != null) {
+                        ViewCompat.setHasTransientState(alreadyMatched, false);
+                        transitionValuesMaps.mItemIdValues.put(itemId, null);
+                    }
+                } else {
+                    ViewCompat.setHasTransientState(view, true);
+                    transitionValuesMaps.mItemIdValues.put(itemId, view);
+                }
+            }
+        }
+    }
+
+    /**
+     * Clear valuesMaps for specified start/end state
+     *
+     * @param start true if the start values should be cleared, false otherwise
+     */
+    void clearValues(boolean start) {
+        if (start) {
+            mStartValues.mViewValues.clear();
+            mStartValues.mIdValues.clear();
+            mStartValues.mItemIdValues.clear();
+        } else {
+            mEndValues.mViewValues.clear();
+            mEndValues.mIdValues.clear();
+            mEndValues.mItemIdValues.clear();
+        }
+    }
+
+    /**
+     * Recursive method which captures values for an entire view hierarchy,
+     * starting at some root view. Transitions without targetIDs will use this
+     * method to capture values for all possible views.
+     *
+     * @param view  The view for which to capture values. Children of this View
+     *              will also be captured, recursively down to the leaf nodes.
+     * @param start true if values are being captured in the start scene, false
+     *              otherwise.
+     */
+    private void captureHierarchy(View view, boolean start) {
+        if (view == null) {
+            return;
+        }
+        int id = view.getId();
+        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
+            return;
+        }
+        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
+            return;
+        }
+        if (mTargetTypeExcludes != null) {
+            int numTypes = mTargetTypeExcludes.size();
+            for (int i = 0; i < numTypes; ++i) {
+                if (mTargetTypeExcludes.get(i).isInstance(view)) {
+                    return;
+                }
+            }
+        }
+        if (view.getParent() instanceof ViewGroup) {
+            TransitionValues values = new TransitionValues();
+            values.view = view;
+            if (start) {
+                captureStartValues(values);
+            } else {
+                captureEndValues(values);
+            }
+            values.mTargetedTransitions.add(this);
+            capturePropagationValues(values);
+            if (start) {
+                addViewValues(mStartValues, view, values);
+            } else {
+                addViewValues(mEndValues, view, values);
+            }
+        }
+        if (view instanceof ViewGroup) {
+            // Don't traverse child hierarchy if there are any child-excludes on this view
+            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
+                return;
+            }
+            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
+                return;
+            }
+            if (mTargetTypeChildExcludes != null) {
+                int numTypes = mTargetTypeChildExcludes.size();
+                for (int i = 0; i < numTypes; ++i) {
+                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
+                        return;
+                    }
+                }
+            }
+            ViewGroup parent = (ViewGroup) view;
+            for (int i = 0; i < parent.getChildCount(); ++i) {
+                captureHierarchy(parent.getChildAt(i), start);
+            }
+        }
+    }
+
+    /**
+     * This method can be called by transitions to get the TransitionValues for
+     * any particular view during the transition-playing process. This might be
+     * necessary, for example, to query the before/after state of related views
+     * for a given transition.
+     */
+    @Nullable
+    public TransitionValues getTransitionValues(@NonNull View view, boolean start) {
+        if (mParent != null) {
+            return mParent.getTransitionValues(view, start);
+        }
+        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
+        return valuesMaps.mViewValues.get(view);
+    }
+
+    /**
+     * Find the matched start or end value for a given View. This is only valid
+     * after playTransition starts. For example, it will be valid in
+     * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)}, but not
+     * in {@link #captureStartValues(TransitionValues)}.
+     *
+     * @param view        The view to find the match for.
+     * @param viewInStart Is View from the start values or end values.
+     * @return The matching TransitionValues for view in either start or end values, depending
+     * on viewInStart or null if there is no match for the given view.
+     */
+    TransitionValues getMatchedTransitionValues(View view, boolean viewInStart) {
+        if (mParent != null) {
+            return mParent.getMatchedTransitionValues(view, viewInStart);
+        }
+        ArrayList<TransitionValues> lookIn = viewInStart ? mStartValuesList : mEndValuesList;
+        if (lookIn == null) {
+            return null;
+        }
+        int count = lookIn.size();
+        int index = -1;
+        for (int i = 0; i < count; i++) {
+            TransitionValues values = lookIn.get(i);
+            if (values == null) {
+                return null;
+            }
+            if (values.view == view) {
+                index = i;
+                break;
+            }
+        }
+        TransitionValues values = null;
+        if (index >= 0) {
+            ArrayList<TransitionValues> matchIn = viewInStart ? mEndValuesList : mStartValuesList;
+            values = matchIn.get(index);
+        }
+        return values;
+    }
+
+    /**
+     * Pauses this transition, sending out calls to {@link
+     * TransitionListener#onTransitionPause(Transition)} to all listeners
+     * and pausing all running animators started by this transition.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void pause(View sceneRoot) {
+        if (!mEnded) {
+            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+            int numOldAnims = runningAnimators.size();
+            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+            for (int i = numOldAnims - 1; i >= 0; i--) {
+                AnimationInfo info = runningAnimators.valueAt(i);
+                if (info.mView != null && windowId.equals(info.mWindowId)) {
+                    Animator anim = runningAnimators.keyAt(i);
+                    AnimatorUtils.pause(anim);
+                }
+            }
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionPause(this);
+                }
+            }
+            mPaused = true;
+        }
+    }
+
+    /**
+     * Resumes this transition, sending out calls to {@link
+     * TransitionListener#onTransitionPause(Transition)} to all listeners
+     * and pausing all running animators started by this transition.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    public void resume(View sceneRoot) {
+        if (mPaused) {
+            if (!mEnded) {
+                ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+                int numOldAnims = runningAnimators.size();
+                WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+                for (int i = numOldAnims - 1; i >= 0; i--) {
+                    AnimationInfo info = runningAnimators.valueAt(i);
+                    if (info.mView != null && windowId.equals(info.mWindowId)) {
+                        Animator anim = runningAnimators.keyAt(i);
+                        AnimatorUtils.resume(anim);
+                    }
+                }
+                if (mListeners != null && mListeners.size() > 0) {
+                    @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                            (ArrayList<TransitionListener>) mListeners.clone();
+                    int numListeners = tmpListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        tmpListeners.get(i).onTransitionResume(this);
+                    }
+                }
+            }
+            mPaused = false;
+        }
+    }
+
+    /**
+     * Called by TransitionManager to play the transition. This calls
+     * createAnimators() to set things up and create all of the animations and then
+     * runAnimations() to actually start the animations.
+     */
+    void playTransition(ViewGroup sceneRoot) {
+        mStartValuesList = new ArrayList<>();
+        mEndValuesList = new ArrayList<>();
+        matchStartAndEnd(mStartValues, mEndValues);
+
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        int numOldAnims = runningAnimators.size();
+        WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+        for (int i = numOldAnims - 1; i >= 0; i--) {
+            Animator anim = runningAnimators.keyAt(i);
+            if (anim != null) {
+                AnimationInfo oldInfo = runningAnimators.get(anim);
+                if (oldInfo != null && oldInfo.mView != null
+                        && windowId.equals(oldInfo.mWindowId)) {
+                    TransitionValues oldValues = oldInfo.mValues;
+                    View oldView = oldInfo.mView;
+                    TransitionValues startValues = getTransitionValues(oldView, true);
+                    TransitionValues endValues = getMatchedTransitionValues(oldView, true);
+                    boolean cancel = (startValues != null || endValues != null)
+                            && oldInfo.mTransition.isTransitionRequired(oldValues, endValues);
+                    if (cancel) {
+                        if (anim.isRunning() || anim.isStarted()) {
+                            if (DBG) {
+                                Log.d(LOG_TAG, "Canceling anim " + anim);
+                            }
+                            anim.cancel();
+                        } else {
+                            if (DBG) {
+                                Log.d(LOG_TAG, "removing anim from info list: " + anim);
+                            }
+                            runningAnimators.remove(anim);
+                        }
+                    }
+                }
+            }
+        }
+
+        createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
+        runAnimators();
+    }
+
+    /**
+     * Returns whether or not the transition should create an Animator, based on the values
+     * captured during {@link #captureStartValues(TransitionValues)} and
+     * {@link #captureEndValues(TransitionValues)}. The default implementation compares the
+     * property values returned from {@link #getTransitionProperties()}, or all property values if
+     * {@code getTransitionProperties()} returns null. Subclasses may override this method to
+     * provide logic more specific to the transition implementation.
+     *
+     * @param startValues the values from captureStartValues, This may be {@code null} if the
+     *                    View did not exist in the start state.
+     * @param endValues   the values from captureEndValues. This may be {@code null} if the View
+     *                    did not exist in the end state.
+     */
+    public boolean isTransitionRequired(@Nullable TransitionValues startValues,
+            @Nullable TransitionValues endValues) {
+        boolean valuesChanged = false;
+        // if startValues null, then transition didn't care to stash values,
+        // and won't get canceled
+        if (startValues != null && endValues != null) {
+            String[] properties = getTransitionProperties();
+            if (properties != null) {
+                for (String property : properties) {
+                    if (isValueChanged(startValues, endValues, property)) {
+                        valuesChanged = true;
+                        break;
+                    }
+                }
+            } else {
+                for (String key : startValues.values.keySet()) {
+                    if (isValueChanged(startValues, endValues, key)) {
+                        valuesChanged = true;
+                        break;
+                    }
+                }
+            }
+        }
+        return valuesChanged;
+    }
+
+    private static boolean isValueChanged(TransitionValues oldValues, TransitionValues newValues,
+            String key) {
+        Object oldValue = oldValues.values.get(key);
+        Object newValue = newValues.values.get(key);
+        boolean changed;
+        if (oldValue == null && newValue == null) {
+            // both are null
+            changed = false;
+        } else if (oldValue == null || newValue == null) {
+            // one is null
+            changed = true;
+        } else {
+            // neither is null
+            changed = !oldValue.equals(newValue);
+        }
+        if (DBG && changed) {
+            Log.d(LOG_TAG, "Transition.playTransition: "
+                    + "oldValue != newValue for " + key
+                    + ": old, new = " + oldValue + ", " + newValue);
+        }
+        return changed;
+    }
+
+    /**
+     * This is a utility method used by subclasses to handle standard parts of
+     * setting up and running an Animator: it sets the {@link #getDuration()
+     * duration} and the {@link #getStartDelay() startDelay}, starts the
+     * animation, and, when the animator ends, calls {@link #end()}.
+     *
+     * @param animator The Animator to be run during this transition.
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void animate(Animator animator) {
+        // TODO: maybe pass auto-end as a boolean parameter?
+        if (animator == null) {
+            end();
+        } else {
+            if (getDuration() >= 0) {
+                animator.setDuration(getDuration());
+            }
+            if (getStartDelay() >= 0) {
+                animator.setStartDelay(getStartDelay());
+            }
+            if (getInterpolator() != null) {
+                animator.setInterpolator(getInterpolator());
+            }
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    end();
+                    animation.removeListener(this);
+                }
+            });
+            animator.start();
+        }
+    }
+
+    /**
+     * This method is called automatically by the transition and
+     * TransitionSet classes prior to a Transition subclass starting;
+     * subclasses should not need to call it directly.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void start() {
+        if (mNumInstances == 0) {
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionStart(this);
+                }
+            }
+            mEnded = false;
+        }
+        mNumInstances++;
+    }
+
+    /**
+     * This method is called automatically by the Transition and
+     * TransitionSet classes when a transition finishes, either because
+     * a transition did nothing (returned a null Animator from
+     * {@link Transition#createAnimator(ViewGroup, TransitionValues,
+     * TransitionValues)}) or because the transition returned a valid
+     * Animator and end() was called in the onAnimationEnd()
+     * callback of the AnimatorListener.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void end() {
+        --mNumInstances;
+        if (mNumInstances == 0) {
+            if (mListeners != null && mListeners.size() > 0) {
+                @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                        (ArrayList<TransitionListener>) mListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onTransitionEnd(this);
+                }
+            }
+            for (int i = 0; i < mStartValues.mItemIdValues.size(); ++i) {
+                View view = mStartValues.mItemIdValues.valueAt(i);
+                if (view != null) {
+                    ViewCompat.setHasTransientState(view, false);
+                }
+            }
+            for (int i = 0; i < mEndValues.mItemIdValues.size(); ++i) {
+                View view = mEndValues.mItemIdValues.valueAt(i);
+                if (view != null) {
+                    ViewCompat.setHasTransientState(view, false);
+                }
+            }
+            mEnded = true;
+        }
+    }
+
+    /**
+     * Force the transition to move to its end state, ending all the animators.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    void forceToEnd(ViewGroup sceneRoot) {
+        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        int numOldAnims = runningAnimators.size();
+        if (sceneRoot != null) {
+            WindowIdImpl windowId = ViewUtils.getWindowId(sceneRoot);
+            for (int i = numOldAnims - 1; i >= 0; i--) {
+                AnimationInfo info = runningAnimators.valueAt(i);
+                if (info.mView != null && windowId != null && windowId.equals(info.mWindowId)) {
+                    Animator anim = runningAnimators.keyAt(i);
+                    anim.end();
+                }
+            }
+        }
+    }
+
+    /**
+     * This method cancels a transition that is currently running.
+     *
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    protected void cancel() {
+        int numAnimators = mCurrentAnimators.size();
+        for (int i = numAnimators - 1; i >= 0; i--) {
+            Animator animator = mCurrentAnimators.get(i);
+            animator.cancel();
+        }
+        if (mListeners != null && mListeners.size() > 0) {
+            @SuppressWarnings("unchecked") ArrayList<TransitionListener> tmpListeners =
+                    (ArrayList<TransitionListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onTransitionCancel(this);
+            }
+        }
+    }
+
+    /**
+     * Adds a listener to the set of listeners that are sent events through the
+     * life of an animation, such as start, repeat, and end.
+     *
+     * @param listener the listener to be added to the current set of listeners
+     *                 for this animation.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition addListener(@NonNull TransitionListener listener) {
+        if (mListeners == null) {
+            mListeners = new ArrayList<>();
+        }
+        mListeners.add(listener);
+        return this;
+    }
+
+    /**
+     * Removes a listener from the set listening to this animation.
+     *
+     * @param listener the listener to be removed from the current set of
+     *                 listeners for this transition.
+     * @return This transition object.
+     */
+    @NonNull
+    public Transition removeListener(@NonNull TransitionListener listener) {
+        if (mListeners == null) {
+            return this;
+        }
+        mListeners.remove(listener);
+        if (mListeners.size() == 0) {
+            mListeners = null;
+        }
+        return this;
+    }
+
+    /**
+     * Sets the algorithm used to calculate two-dimensional interpolation.
+     * <p>
+     * Transitions such as {@link android.transition.ChangeBounds} move Views, typically
+     * in a straight path between the start and end positions. Applications that desire to
+     * have these motions move in a curve can change how Views interpolate in two dimensions
+     * by extending PathMotion and implementing
+     * {@link android.transition.PathMotion#getPath(float, float, float, float)}.
+     * </p>
+     *
+     * @param pathMotion Algorithm object to use for determining how to interpolate in two
+     *                   dimensions. If null, a straight-path algorithm will be used.
+     * @see android.transition.ArcMotion
+     * @see PatternPathMotion
+     * @see android.transition.PathMotion
+     */
+    public void setPathMotion(@Nullable PathMotion pathMotion) {
+        if (pathMotion == null) {
+            mPathMotion = STRAIGHT_PATH_MOTION;
+        } else {
+            mPathMotion = pathMotion;
+        }
+    }
+
+    /**
+     * Returns the algorithm object used to interpolate along two dimensions. This is typically
+     * used to determine the View motion between two points.
+     *
+     * @return The algorithm object used to interpolate along two dimensions.
+     * @see android.transition.ArcMotion
+     * @see PatternPathMotion
+     * @see android.transition.PathMotion
+     */
+    @NonNull
+    public PathMotion getPathMotion() {
+        return mPathMotion;
+    }
+
+    /**
+     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+     * that there is no epicenter in the Transition and onGetEpicenter() will return null.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+     */
+    public void setEpicenterCallback(@Nullable EpicenterCallback epicenterCallback) {
+        mEpicenterCallback = epicenterCallback;
+    }
+
+    /**
+     * Returns the callback used to find the epicenter of the Transition.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     *
+     * @return the callback used to find the epicenter of the Transition.
+     */
+    @Nullable
+    public EpicenterCallback getEpicenterCallback() {
+        return mEpicenterCallback;
+    }
+
+    /**
+     * Returns the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     *
+     * @return the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    @Nullable
+    public Rect getEpicenter() {
+        if (mEpicenterCallback == null) {
+            return null;
+        }
+        return mEpicenterCallback.onGetEpicenter(this);
+    }
+
+    /**
+     * Sets the method for determining Animator start delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @param transitionPropagation The class used to determine the start delay of
+     *                              Animators created by this Transition. A null value
+     *                              indicates that no delay should be used.
+     */
+    public void setPropagation(@Nullable TransitionPropagation transitionPropagation) {
+        mPropagation = transitionPropagation;
+    }
+
+    /**
+     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator
+     * start
+     * delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     *
+     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+     * delays. This is null by default.
+     */
+    @Nullable
+    public TransitionPropagation getPropagation() {
+        return mPropagation;
+    }
+
+    /**
+     * Captures TransitionPropagation values for the given view and the
+     * hierarchy underneath it.
+     */
+    void capturePropagationValues(TransitionValues transitionValues) {
+        if (mPropagation != null && !transitionValues.values.isEmpty()) {
+            String[] propertyNames = mPropagation.getPropagationProperties();
+            if (propertyNames == null) {
+                return;
+            }
+            boolean containsAll = true;
+            for (int i = 0; i < propertyNames.length; i++) {
+                if (!transitionValues.values.containsKey(propertyNames[i])) {
+                    containsAll = false;
+                    break;
+                }
+            }
+            if (!containsAll) {
+                mPropagation.captureValues(transitionValues);
+            }
+        }
+    }
+
+    Transition setSceneRoot(ViewGroup sceneRoot) {
+        mSceneRoot = sceneRoot;
+        return this;
+    }
+
+    void setCanRemoveViews(boolean canRemoveViews) {
+        mCanRemoveViews = canRemoveViews;
+    }
+
+    @Override
+    public String toString() {
+        return toString("");
+    }
+
+    @Override
+    public Transition clone() {
+        try {
+            Transition clone = (Transition) super.clone();
+            clone.mAnimators = new ArrayList<>();
+            clone.mStartValues = new TransitionValuesMaps();
+            clone.mEndValues = new TransitionValuesMaps();
+            clone.mStartValuesList = null;
+            clone.mEndValuesList = null;
+            return clone;
+        } catch (CloneNotSupportedException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the name of this Transition. This name is used internally to distinguish
+     * between different transitions to determine when interrupting transitions overlap.
+     * For example, a ChangeBounds running on the same target view as another ChangeBounds
+     * should determine whether the old transition is animating to different end values
+     * and should be canceled in favor of the new transition.
+     *
+     * <p>By default, a Transition's name is simply the value of {@link Class#getName()},
+     * but subclasses are free to override and return something different.</p>
+     *
+     * @return The name of this transition.
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    String toString(String indent) {
+        String result = indent + getClass().getSimpleName() + "@"
+                + Integer.toHexString(hashCode()) + ": ";
+        if (mDuration != -1) {
+            result += "dur(" + mDuration + ") ";
+        }
+        if (mStartDelay != -1) {
+            result += "dly(" + mStartDelay + ") ";
+        }
+        if (mInterpolator != null) {
+            result += "interp(" + mInterpolator + ") ";
+        }
+        if (mTargetIds.size() > 0 || mTargets.size() > 0) {
+            result += "tgts(";
+            if (mTargetIds.size() > 0) {
+                for (int i = 0; i < mTargetIds.size(); ++i) {
+                    if (i > 0) {
+                        result += ", ";
+                    }
+                    result += mTargetIds.get(i);
+                }
+            }
+            if (mTargets.size() > 0) {
+                for (int i = 0; i < mTargets.size(); ++i) {
+                    if (i > 0) {
+                        result += ", ";
+                    }
+                    result += mTargets.get(i);
+                }
+            }
+            result += ")";
+        }
+        return result;
+    }
+
+    /**
+     * A transition listener receives notifications from a transition.
+     * Notifications indicate transition lifecycle events.
+     */
+    public interface TransitionListener {
+
+        /**
+         * Notification about the start of the transition.
+         *
+         * @param transition The started transition.
+         */
+        void onTransitionStart(@NonNull Transition transition);
+
+        /**
+         * Notification about the end of the transition. Canceled transitions
+         * will always notify listeners of both the cancellation and end
+         * events. That is, {@link #onTransitionEnd(Transition)} is always called,
+         * regardless of whether the transition was canceled or played
+         * through to completion.
+         *
+         * @param transition The transition which reached its end.
+         */
+        void onTransitionEnd(@NonNull Transition transition);
+
+        /**
+         * Notification about the cancellation of the transition.
+         * Note that cancel may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state on target objects which was set at
+         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
+         * createAnimator()} time.
+         *
+         * @param transition The transition which was canceled.
+         */
+        void onTransitionCancel(@NonNull Transition transition);
+
+        /**
+         * Notification when a transition is paused.
+         * Note that createAnimator() may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state on target objects which was set at
+         * {@link #createAnimator(android.view.ViewGroup, TransitionValues, TransitionValues)
+         * createAnimator()} time.
+         *
+         * @param transition The transition which was paused.
+         */
+        void onTransitionPause(@NonNull Transition transition);
+
+        /**
+         * Notification when a transition is resumed.
+         * Note that resume() may be called by a parent {@link TransitionSet} on
+         * a child transition which has not yet started. This allows the child
+         * transition to restore state which may have changed in an earlier call
+         * to {@link #onTransitionPause(Transition)}.
+         *
+         * @param transition The transition which was resumed.
+         */
+        void onTransitionResume(@NonNull Transition transition);
+    }
+
+    /**
+     * Holds information about each animator used when a new transition starts
+     * while other transitions are still running to determine whether a running
+     * animation should be canceled or a new animation noop'd. The structure holds
+     * information about the state that an animation is going to, to be compared to
+     * end state of a new animation.
+     */
+    private static class AnimationInfo {
+
+        View mView;
+
+        String mName;
+
+        TransitionValues mValues;
+
+        WindowIdImpl mWindowId;
+
+        Transition mTransition;
+
+        AnimationInfo(View view, String name, Transition transition, WindowIdImpl windowId,
+                TransitionValues values) {
+            mView = view;
+            mName = name;
+            mValues = values;
+            mWindowId = windowId;
+            mTransition = transition;
+        }
+    }
+
+    /**
+     * Utility class for managing typed ArrayLists efficiently. In particular, this
+     * can be useful for lists that we don't expect to be used often (eg, the exclude
+     * lists), so we'd like to keep them nulled out by default. This causes the code to
+     * become tedious, with constant null checks, code to allocate when necessary,
+     * and code to null out the reference when the list is empty. This class encapsulates
+     * all of that functionality into simple add()/remove() methods which perform the
+     * necessary checks, allocation/null-out as appropriate, and return the
+     * resulting list.
+     */
+    private static class ArrayListManager {
+
+        /**
+         * Add the specified item to the list, returning the resulting list.
+         * The returned list can either the be same list passed in or, if that
+         * list was null, the new list that was created.
+         *
+         * Note that the list holds unique items; if the item already exists in the
+         * list, the list is not modified.
+         */
+        static <T> ArrayList<T> add(ArrayList<T> list, T item) {
+            if (list == null) {
+                list = new ArrayList<>();
+            }
+            if (!list.contains(item)) {
+                list.add(item);
+            }
+            return list;
+        }
+
+        /**
+         * Remove the specified item from the list, returning the resulting list.
+         * The returned list can either the be same list passed in or, if that
+         * list becomes empty as a result of the remove(), the new list was created.
+         */
+        static <T> ArrayList<T> remove(ArrayList<T> list, T item) {
+            if (list != null) {
+                list.remove(item);
+                if (list.isEmpty()) {
+                    list = null;
+                }
+            }
+            return list;
+        }
+    }
+
+    /**
+     * Class to get the epicenter of Transition. Use
+     * {@link #setEpicenterCallback(EpicenterCallback)} to set the callback used to calculate the
+     * epicenter of the Transition. Override {@link #getEpicenter()} to return the rectangular
+     * region in screen coordinates of the epicenter of the transition.
+     *
+     * @see #setEpicenterCallback(EpicenterCallback)
+     */
+    public abstract static class EpicenterCallback {
+
+        /**
+         * Implementers must override to return the epicenter of the Transition in screen
+         * coordinates. Transitions like {@link android.transition.Explode} depend upon
+         * an epicenter for the Transition. In Explode, Views move toward or away from the
+         * center of the epicenter Rect along the vector between the epicenter and the center
+         * of the View appearing and disappearing. Some Transitions, such as
+         * {@link android.transition.Fade} pay no attention to the epicenter.
+         *
+         * @param transition The transition for which the epicenter applies.
+         * @return The Rect region of the epicenter of <code>transition</code> or null if
+         * there is no epicenter.
+         */
+        public abstract Rect onGetEpicenter(@NonNull Transition transition);
+    }
+
+}
diff --git a/androidx/transition/TransitionActivity.java b/androidx/transition/TransitionActivity.java
new file mode 100644
index 0000000..a155a65
--- /dev/null
+++ b/androidx/transition/TransitionActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 androidx.transition;
+
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.FragmentActivity;
+import androidx.transition.test.R;
+
+public class TransitionActivity extends FragmentActivity {
+
+    private LinearLayout mRoot;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_transition);
+        mRoot = findViewById(R.id.root);
+    }
+
+    ViewGroup getRoot() {
+        return mRoot;
+    }
+
+}
diff --git a/androidx/transition/TransitionInflater.java b/androidx/transition/TransitionInflater.java
new file mode 100644
index 0000000..4570fe9
--- /dev/null
+++ b/androidx/transition/TransitionInflater.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.InflateException;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.collection.ArrayMap;
+import androidx.core.content.res.TypedArrayUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+
+/**
+ * This class inflates scenes and transitions from resource files.
+ */
+public class TransitionInflater {
+
+    private static final Class<?>[] CONSTRUCTOR_SIGNATURE =
+            new Class[]{Context.class, AttributeSet.class};
+    private static final ArrayMap<String, Constructor> CONSTRUCTORS = new ArrayMap<>();
+
+    private final Context mContext;
+
+    private TransitionInflater(@NonNull Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Obtains the TransitionInflater from the given context.
+     */
+    public static TransitionInflater from(Context context) {
+        return new TransitionInflater(context);
+    }
+
+    /**
+     * Loads a {@link Transition} object from a resource
+     *
+     * @param resource The resource id of the transition to load
+     * @return The loaded Transition object
+     * @throws android.content.res.Resources.NotFoundException when the
+     *                                                         transition cannot be loaded
+     */
+    public Transition inflateTransition(int resource) {
+        XmlResourceParser parser = mContext.getResources().getXml(resource);
+        try {
+            return createTransitionFromXml(parser, Xml.asAttributeSet(parser), null);
+        } catch (XmlPullParserException e) {
+            throw new InflateException(e.getMessage(), e);
+        } catch (IOException e) {
+            throw new InflateException(
+                    parser.getPositionDescription() + ": " + e.getMessage(), e);
+        } finally {
+            parser.close();
+        }
+    }
+
+    /**
+     * Loads a {@link TransitionManager} object from a resource
+     *
+     * @param resource The resource id of the transition manager to load
+     * @return The loaded TransitionManager object
+     * @throws android.content.res.Resources.NotFoundException when the
+     *                                                         transition manager cannot be loaded
+     */
+    public TransitionManager inflateTransitionManager(int resource, ViewGroup sceneRoot) {
+        XmlResourceParser parser = mContext.getResources().getXml(resource);
+        try {
+            return createTransitionManagerFromXml(parser, Xml.asAttributeSet(parser), sceneRoot);
+        } catch (XmlPullParserException e) {
+            InflateException ex = new InflateException(e.getMessage());
+            ex.initCause(e);
+            throw ex;
+        } catch (IOException e) {
+            InflateException ex = new InflateException(
+                    parser.getPositionDescription()
+                            + ": " + e.getMessage());
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            parser.close();
+        }
+    }
+
+    //
+    // Transition loading
+    //
+    private Transition createTransitionFromXml(XmlPullParser parser,
+            AttributeSet attrs, Transition parent)
+            throws XmlPullParserException, IOException {
+
+        Transition transition = null;
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        TransitionSet transitionSet = (parent instanceof TransitionSet)
+                ? (TransitionSet) parent : null;
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+            if ("fade".equals(name)) {
+                transition = new Fade(mContext, attrs);
+            } else if ("changeBounds".equals(name)) {
+                transition = new ChangeBounds(mContext, attrs);
+            } else if ("slide".equals(name)) {
+                transition = new Slide(mContext, attrs);
+            } else if ("explode".equals(name)) {
+                transition = new Explode(mContext, attrs);
+            } else if ("changeImageTransform".equals(name)) {
+                transition = new ChangeImageTransform(mContext, attrs);
+            } else if ("changeTransform".equals(name)) {
+                transition = new ChangeTransform(mContext, attrs);
+            } else if ("changeClipBounds".equals(name)) {
+                transition = new ChangeClipBounds(mContext, attrs);
+            } else if ("autoTransition".equals(name)) {
+                transition = new AutoTransition(mContext, attrs);
+            } else if ("changeScroll".equals(name)) {
+                transition = new ChangeScroll(mContext, attrs);
+            } else if ("transitionSet".equals(name)) {
+                transition = new TransitionSet(mContext, attrs);
+            } else if ("transition".equals(name)) {
+                transition = (Transition) createCustom(attrs, Transition.class, "transition");
+            } else if ("targets".equals(name)) {
+                getTargetIds(parser, attrs, parent);
+            } else if ("arcMotion".equals(name)) {
+                if (parent == null) {
+                    throw new RuntimeException("Invalid use of arcMotion element");
+                }
+                parent.setPathMotion(new ArcMotion(mContext, attrs));
+            } else if ("pathMotion".equals(name)) {
+                if (parent == null) {
+                    throw new RuntimeException("Invalid use of pathMotion element");
+                }
+                parent.setPathMotion((PathMotion) createCustom(attrs, PathMotion.class,
+                        "pathMotion"));
+            } else if ("patternPathMotion".equals(name)) {
+                if (parent == null) {
+                    throw new RuntimeException("Invalid use of patternPathMotion element");
+                }
+                parent.setPathMotion(new PatternPathMotion(mContext, attrs));
+            } else {
+                throw new RuntimeException("Unknown scene name: " + parser.getName());
+            }
+            if (transition != null) {
+                if (!parser.isEmptyElementTag()) {
+                    createTransitionFromXml(parser, attrs, transition);
+                }
+                if (transitionSet != null) {
+                    transitionSet.addTransition(transition);
+                    transition = null;
+                } else if (parent != null) {
+                    throw new InflateException("Could not add transition to another transition.");
+                }
+            }
+        }
+
+        return transition;
+    }
+
+    private Object createCustom(AttributeSet attrs, Class expectedType, String tag) {
+        String className = attrs.getAttributeValue(null, "class");
+
+        if (className == null) {
+            throw new InflateException(tag + " tag must have a 'class' attribute");
+        }
+
+        try {
+            synchronized (CONSTRUCTORS) {
+                Constructor constructor = CONSTRUCTORS.get(className);
+                if (constructor == null) {
+                    @SuppressWarnings("unchecked")
+                    Class<?> c = mContext.getClassLoader().loadClass(className)
+                            .asSubclass(expectedType);
+                    if (c != null) {
+                        constructor = c.getConstructor(CONSTRUCTOR_SIGNATURE);
+                        constructor.setAccessible(true);
+                        CONSTRUCTORS.put(className, constructor);
+                    }
+                }
+                //noinspection ConstantConditions
+                return constructor.newInstance(mContext, attrs);
+            }
+        } catch (Exception e) {
+            throw new InflateException("Could not instantiate " + expectedType + " class "
+                    + className, e);
+        }
+    }
+
+    private void getTargetIds(XmlPullParser parser,
+            AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+            if (name.equals("target")) {
+                TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_TARGET);
+                int id = TypedArrayUtils.getNamedResourceId(a, parser, "targetId",
+                        Styleable.TransitionTarget.TARGET_ID, 0);
+                String transitionName;
+                if (id != 0) {
+                    transition.addTarget(id);
+                } else if ((id = TypedArrayUtils.getNamedResourceId(a, parser, "excludeId",
+                        Styleable.TransitionTarget.EXCLUDE_ID, 0)) != 0) {
+                    transition.excludeTarget(id, true);
+                } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser, "targetName",
+                        Styleable.TransitionTarget.TARGET_NAME)) != null) {
+                    transition.addTarget(transitionName);
+                } else if ((transitionName = TypedArrayUtils.getNamedString(a, parser,
+                        "excludeName", Styleable.TransitionTarget.EXCLUDE_NAME)) != null) {
+                    transition.excludeTarget(transitionName, true);
+                } else {
+                    String className = TypedArrayUtils.getNamedString(a, parser,
+                            "excludeClass", Styleable.TransitionTarget.EXCLUDE_CLASS);
+                    try {
+                        if (className != null) {
+                            Class clazz = Class.forName(className);
+                            transition.excludeTarget(clazz, true);
+                        } else if ((className = TypedArrayUtils.getNamedString(a, parser,
+                                "targetClass", Styleable.TransitionTarget.TARGET_CLASS)) != null) {
+                            Class clazz = Class.forName(className);
+                            transition.addTarget(clazz);
+                        }
+                    } catch (ClassNotFoundException e) {
+                        a.recycle();
+                        throw new RuntimeException("Could not create " + className, e);
+                    }
+                }
+                a.recycle();
+            } else {
+                throw new RuntimeException("Unknown scene name: " + parser.getName());
+            }
+        }
+    }
+
+    //
+    // TransitionManager loading
+    //
+
+    private TransitionManager createTransitionManagerFromXml(XmlPullParser parser,
+            AttributeSet attrs, ViewGroup sceneRoot) throws XmlPullParserException, IOException {
+
+        // Make sure we are on a start tag.
+        int type;
+        int depth = parser.getDepth();
+        TransitionManager transitionManager = null;
+
+        while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
+                && type != XmlPullParser.END_DOCUMENT) {
+
+            if (type != XmlPullParser.START_TAG) {
+                continue;
+            }
+
+            String name = parser.getName();
+            if (name.equals("transitionManager")) {
+                transitionManager = new TransitionManager();
+            } else if (name.equals("transition") && (transitionManager != null)) {
+                loadTransition(attrs, parser, sceneRoot, transitionManager);
+            } else {
+                throw new RuntimeException("Unknown scene name: " + parser.getName());
+            }
+        }
+        return transitionManager;
+    }
+
+    private void loadTransition(AttributeSet attrs, XmlPullParser parser, ViewGroup sceneRoot,
+            TransitionManager transitionManager) throws Resources.NotFoundException {
+
+        TypedArray a = mContext.obtainStyledAttributes(attrs, Styleable.TRANSITION_MANAGER);
+        int transitionId = TypedArrayUtils.getNamedResourceId(a, parser, "transition",
+                Styleable.TransitionManager.TRANSITION, -1);
+        int fromId = TypedArrayUtils.getNamedResourceId(a, parser, "fromScene",
+                Styleable.TransitionManager.FROM_SCENE, -1);
+        Scene fromScene = (fromId < 0) ? null : Scene.getSceneForLayout(sceneRoot, fromId,
+                mContext);
+        int toId = TypedArrayUtils.getNamedResourceId(a, parser, "toScene",
+                Styleable.TransitionManager.TO_SCENE, -1);
+        Scene toScene = (toId < 0) ? null : Scene.getSceneForLayout(sceneRoot, toId, mContext);
+
+        if (transitionId >= 0) {
+            Transition transition = inflateTransition(transitionId);
+            if (transition != null) {
+                if (toScene == null) {
+                    throw new RuntimeException("No toScene for transition ID " + transitionId);
+                }
+                if (fromScene == null) {
+                    transitionManager.setTransition(toScene, transition);
+                } else {
+                    transitionManager.setTransition(fromScene, toScene, transition);
+                }
+            }
+        }
+        a.recycle();
+    }
+
+}
diff --git a/androidx/transition/TransitionInflaterTest.java b/androidx/transition/TransitionInflaterTest.java
new file mode 100644
index 0000000..a0cc8d8
--- /dev/null
+++ b/androidx/transition/TransitionInflaterTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.support.test.filters.MediumTest;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.transition.test.R;
+
+import org.junit.Test;
+
+import java.util.List;
+
+@MediumTest
+public class TransitionInflaterTest extends BaseTest {
+
+    @Test
+    public void testInflationConstructors() throws Throwable {
+        TransitionInflater inflater = TransitionInflater.from(rule.getActivity());
+        Transition transition = inflater.inflateTransition(R.transition.transition_constructors);
+        assertTrue(transition instanceof TransitionSet);
+        TransitionSet set = (TransitionSet) transition;
+        assertEquals(10, set.getTransitionCount());
+    }
+
+    @Test
+    public void testInflation() {
+        TransitionInflater inflater = TransitionInflater.from(rule.getActivity());
+        verifyFadeProperties(inflater.inflateTransition(R.transition.fade));
+        verifyChangeBoundsProperties(inflater.inflateTransition(R.transition.change_bounds));
+        verifySlideProperties(inflater.inflateTransition(R.transition.slide));
+        verifyExplodeProperties(inflater.inflateTransition(R.transition.explode));
+        verifyChangeImageTransformProperties(
+                inflater.inflateTransition(R.transition.change_image_transform));
+        verifyChangeTransformProperties(inflater.inflateTransition(R.transition.change_transform));
+        verifyChangeClipBoundsProperties(
+                inflater.inflateTransition(R.transition.change_clip_bounds));
+        verifyAutoTransitionProperties(inflater.inflateTransition(R.transition.auto_transition));
+        verifyChangeScrollProperties(inflater.inflateTransition(R.transition.change_scroll));
+        verifyTransitionSetProperties(inflater.inflateTransition(R.transition.transition_set));
+        verifyCustomTransitionProperties(
+                inflater.inflateTransition(R.transition.custom_transition));
+        verifyTargetIds(inflater.inflateTransition(R.transition.target_ids));
+        verifyTargetNames(inflater.inflateTransition(R.transition.target_names));
+        verifyTargetClass(inflater.inflateTransition(R.transition.target_classes));
+        verifyArcMotion(inflater.inflateTransition(R.transition.arc_motion));
+        verifyCustomPathMotion(inflater.inflateTransition(R.transition.custom_path_motion));
+        verifyPatternPathMotion(inflater.inflateTransition(R.transition.pattern_path_motion));
+    }
+
+    // TODO: Add test for TransitionManager
+
+    private void verifyFadeProperties(Transition transition) {
+        assertTrue(transition instanceof Fade);
+        Fade fade = (Fade) transition;
+        assertEquals(Fade.OUT, fade.getMode());
+    }
+
+    private void verifyChangeBoundsProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeBounds);
+        ChangeBounds changeBounds = (ChangeBounds) transition;
+        assertTrue(changeBounds.getResizeClip());
+    }
+
+    private void verifySlideProperties(Transition transition) {
+        assertTrue(transition instanceof Slide);
+        Slide slide = (Slide) transition;
+        assertEquals(Gravity.TOP, slide.getSlideEdge());
+    }
+
+    private void verifyExplodeProperties(Transition transition) {
+        assertTrue(transition instanceof Explode);
+        Visibility visibility = (Visibility) transition;
+        assertEquals(Visibility.MODE_IN, visibility.getMode());
+    }
+
+    private void verifyChangeImageTransformProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeImageTransform);
+    }
+
+    private void verifyChangeTransformProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeTransform);
+        ChangeTransform changeTransform = (ChangeTransform) transition;
+        assertFalse(changeTransform.getReparent());
+        assertFalse(changeTransform.getReparentWithOverlay());
+    }
+
+    private void verifyChangeClipBoundsProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeClipBounds);
+    }
+
+    private void verifyAutoTransitionProperties(Transition transition) {
+        assertTrue(transition instanceof AutoTransition);
+    }
+
+    private void verifyChangeScrollProperties(Transition transition) {
+        assertTrue(transition instanceof ChangeScroll);
+    }
+
+    private void verifyTransitionSetProperties(Transition transition) {
+        assertTrue(transition instanceof TransitionSet);
+        TransitionSet set = (TransitionSet) transition;
+        assertEquals(TransitionSet.ORDERING_SEQUENTIAL, set.getOrdering());
+        assertEquals(2, set.getTransitionCount());
+        assertTrue(set.getTransitionAt(0) instanceof ChangeBounds);
+        assertTrue(set.getTransitionAt(1) instanceof Fade);
+    }
+
+    private void verifyCustomTransitionProperties(Transition transition) {
+        assertTrue(transition instanceof CustomTransition);
+    }
+
+    private void verifyTargetIds(Transition transition) {
+        List<Integer> targets = transition.getTargetIds();
+        assertNotNull(targets);
+        assertEquals(2, targets.size());
+        assertEquals(R.id.hello, (int) targets.get(0));
+        assertEquals(R.id.world, (int) targets.get(1));
+    }
+
+    private void verifyTargetNames(Transition transition) {
+        List<String> targets = transition.getTargetNames();
+        assertNotNull(targets);
+        assertEquals(2, targets.size());
+        assertEquals("hello", targets.get(0));
+        assertEquals("world", targets.get(1));
+    }
+
+    private void verifyTargetClass(Transition transition) {
+        List<Class> targets = transition.getTargetTypes();
+        assertNotNull(targets);
+        assertEquals(2, targets.size());
+        assertEquals(TextView.class, targets.get(0));
+        assertEquals(ImageView.class, targets.get(1));
+    }
+
+    private void verifyArcMotion(Transition transition) {
+        assertNotNull(transition);
+        PathMotion motion = transition.getPathMotion();
+        assertNotNull(motion);
+        assertTrue(motion instanceof ArcMotion);
+        ArcMotion arcMotion = (ArcMotion) motion;
+        assertEquals(1f, arcMotion.getMinimumVerticalAngle(), 0.01f);
+        assertEquals(2f, arcMotion.getMinimumHorizontalAngle(), 0.01f);
+        assertEquals(53f, arcMotion.getMaximumAngle(), 0.01f);
+    }
+
+    private void verifyCustomPathMotion(Transition transition) {
+        assertNotNull(transition);
+        PathMotion motion = transition.getPathMotion();
+        assertNotNull(motion);
+        assertTrue(motion instanceof CustomPathMotion);
+    }
+
+    private void verifyPatternPathMotion(Transition transition) {
+        assertNotNull(transition);
+        PathMotion motion = transition.getPathMotion();
+        assertNotNull(motion);
+        assertTrue(motion instanceof PatternPathMotion);
+        PatternPathMotion pattern = (PatternPathMotion) motion;
+        Path path = pattern.getPatternPath();
+        PathMeasure measure = new PathMeasure(path, false);
+        assertEquals(200f, measure.getLength(), 0.1f);
+    }
+
+    public static class CustomTransition extends Transition {
+        public CustomTransition() {
+            fail("Default constructor was not expected");
+        }
+
+        @SuppressWarnings("unused") // This constructor is used in XML
+        public CustomTransition(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        }
+
+        @Override
+        public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        }
+    }
+
+    public static class CustomPathMotion extends PathMotion {
+        public CustomPathMotion() {
+            fail("default constructor shouldn't be called.");
+        }
+
+        public CustomPathMotion(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+
+        @Override
+        public Path getPath(float startX, float startY, float endX, float endY) {
+            return null;
+        }
+    }
+
+    public static class InflationFade extends Fade {
+        public InflationFade(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationChangeBounds extends ChangeBounds {
+        public InflationChangeBounds(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationSlide extends Slide {
+        public InflationSlide(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationTransitionSet extends TransitionSet {
+        public InflationTransitionSet(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationChangeImageTransform extends ChangeImageTransform {
+        public InflationChangeImageTransform(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationChangeTransform extends ChangeTransform {
+        public InflationChangeTransform(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationAutoTransition extends AutoTransition {
+        public InflationAutoTransition(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationChangeClipBounds extends ChangeClipBounds {
+        public InflationChangeClipBounds(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationChangeScroll extends ChangeScroll {
+        public InflationChangeScroll(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+    public static class InflationExplode extends Explode {
+        public InflationExplode(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+
+}
diff --git a/androidx/transition/TransitionListenerAdapter.java b/androidx/transition/TransitionListenerAdapter.java
new file mode 100644
index 0000000..6f93260
--- /dev/null
+++ b/androidx/transition/TransitionListenerAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import androidx.annotation.NonNull;
+
+/**
+ * This adapter class provides empty implementations of the methods from {@link
+ * Transition.TransitionListener}.
+ * Any custom listener that cares only about a subset of the methods of this listener can
+ * simply subclass this adapter class instead of implementing the interface directly.
+ */
+public class TransitionListenerAdapter implements Transition.TransitionListener {
+
+    @Override
+    public void onTransitionStart(@NonNull Transition transition) {
+    }
+
+    @Override
+    public void onTransitionEnd(@NonNull Transition transition) {
+    }
+
+    @Override
+    public void onTransitionCancel(@NonNull Transition transition) {
+    }
+
+    @Override
+    public void onTransitionPause(@NonNull Transition transition) {
+    }
+
+    @Override
+    public void onTransitionResume(@NonNull Transition transition) {
+    }
+
+}
diff --git a/androidx/transition/TransitionManager.java b/androidx/transition/TransitionManager.java
new file mode 100644
index 0000000..517d2e3
--- /dev/null
+++ b/androidx/transition/TransitionManager.java
@@ -0,0 +1,431 @@
+/*
+ * 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 androidx.transition;
+
+import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.collection.ArrayMap;
+import androidx.core.view.ViewCompat;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This class manages the set of transitions that fire when there is a
+ * change of {@link Scene}. To use the manager, add scenes along with
+ * transition objects with calls to {@link #setTransition(Scene, Transition)}
+ * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
+ * transitions for scene changes is not required; by default, a Scene change
+ * will use {@link AutoTransition} to do something reasonable for most
+ * situations. Specifying other transitions for particular scene changes is
+ * only necessary if the application wants different transition behavior
+ * in these situations.
+ *
+ * <p>TransitionManagers can be declared in XML resource files inside the
+ * <code>res/transition</code> directory. TransitionManager resources consist of
+ * the <code>transitionManager</code>tag name, containing one or more
+ * <code>transition</code> tags, each of which describe the relationship of
+ * that transition to the from/to scene information in that tag.
+ * For example, here is a resource file that declares several scene
+ * transitions:</p>
+ *
+ * <pre>
+ *     &lt;transitionManager xmlns:android="http://schemas.android.com/apk/res/android"&gt;
+ *         &lt;transition android:fromScene="@layout/transition_scene1"
+ *                     android:toScene="@layout/transition_scene2"
+ *                     android:transition="@transition/changebounds"/&gt;
+ *         &lt;transition android:fromScene="@layout/transition_scene2"
+ *                     android:toScene="@layout/transition_scene1"
+ *                     android:transition="@transition/changebounds"/&gt;
+ *         &lt;transition android:toScene="@layout/transition_scene3"
+ *                     android:transition="@transition/changebounds_fadein_together"/&gt;
+ *         &lt;transition android:fromScene="@layout/transition_scene3"
+ *                     android:toScene="@layout/transition_scene1"
+ *                     android:transition="@transition/changebounds_fadeout_sequential"/&gt;
+ *         &lt;transition android:fromScene="@layout/transition_scene3"
+ *                     android:toScene="@layout/transition_scene2"
+ *                     android:transition="@transition/changebounds_fadeout_sequential"/&gt;
+ *     &lt;/transitionManager&gt;
+ * </pre>
+ *
+ * <p>For each of the <code>fromScene</code> and <code>toScene</code> attributes,
+ * there is a reference to a standard XML layout file. This is equivalent to
+ * creating a scene from a layout in code by calling
+ * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the
+ * <code>transition</code> attribute, there is a reference to a resource
+ * file in the <code>res/transition</code> directory which describes that
+ * transition.</p>
+ */
+public class TransitionManager {
+
+    private static final String LOG_TAG = "TransitionManager";
+
+    private static Transition sDefaultTransition = new AutoTransition();
+
+    private ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<>();
+    private ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<>();
+    private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
+            sRunningTransitions = new ThreadLocal<>();
+    private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<>();
+
+    /**
+     * Sets a specific transition to occur when the given scene is entered.
+     *
+     * @param scene      The scene which, when applied, will cause the given
+     *                   transition to run.
+     * @param transition The transition that will play when the given scene is
+     *                   entered. A value of null will result in the default behavior of
+     *                   using the default transition instead.
+     */
+    public void setTransition(@NonNull Scene scene, @Nullable Transition transition) {
+        mSceneTransitions.put(scene, transition);
+    }
+
+    /**
+     * Sets a specific transition to occur when the given pair of scenes is
+     * exited/entered.
+     *
+     * @param fromScene  The scene being exited when the given transition will
+     *                   be run
+     * @param toScene    The scene being entered when the given transition will
+     *                   be run
+     * @param transition The transition that will play when the given scene is
+     *                   entered. A value of null will result in the default behavior of
+     *                   using the default transition instead.
+     */
+    public void setTransition(@NonNull Scene fromScene, @NonNull Scene toScene,
+            @Nullable Transition transition) {
+        ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
+        if (sceneTransitionMap == null) {
+            sceneTransitionMap = new ArrayMap<>();
+            mScenePairTransitions.put(toScene, sceneTransitionMap);
+        }
+        sceneTransitionMap.put(fromScene, transition);
+    }
+
+    /**
+     * Returns the Transition for the given scene being entered. The result
+     * depends not only on the given scene, but also the scene which the
+     * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
+     *
+     * @param scene The scene being entered
+     * @return The Transition to be used for the given scene change. If no
+     * Transition was specified for this scene change, the default transition
+     * will be used instead.
+     */
+    private Transition getTransition(Scene scene) {
+        Transition transition;
+        ViewGroup sceneRoot = scene.getSceneRoot();
+        if (sceneRoot != null) {
+            // TODO: cached in Scene instead? long-term, cache in View itself
+            Scene currScene = Scene.getCurrentScene(sceneRoot);
+            if (currScene != null) {
+                ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions
+                        .get(scene);
+                if (sceneTransitionMap != null) {
+                    transition = sceneTransitionMap.get(currScene);
+                    if (transition != null) {
+                        return transition;
+                    }
+                }
+            }
+        }
+        transition = mSceneTransitions.get(scene);
+        return (transition != null) ? transition : sDefaultTransition;
+    }
+
+    /**
+     * This is where all of the work of a transition/scene-change is
+     * orchestrated. This method captures the start values for the given
+     * transition, exits the current Scene, enters the new scene, captures
+     * the end values for the transition, and finally plays the
+     * resulting values-populated transition.
+     *
+     * @param scene      The scene being entered
+     * @param transition The transition to play for this scene change
+     */
+    private static void changeScene(Scene scene, Transition transition) {
+        final ViewGroup sceneRoot = scene.getSceneRoot();
+
+        if (!sPendingTransitions.contains(sceneRoot)) {
+            if (transition == null) {
+                scene.enter();
+            } else {
+                sPendingTransitions.add(sceneRoot);
+
+                Transition transitionClone = transition.clone();
+                transitionClone.setSceneRoot(sceneRoot);
+
+                Scene oldScene = Scene.getCurrentScene(sceneRoot);
+                if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
+                    transitionClone.setCanRemoveViews(true);
+                }
+
+                sceneChangeSetup(sceneRoot, transitionClone);
+
+                scene.enter();
+
+                sceneChangeRunTransition(sceneRoot, transitionClone);
+            }
+        }
+    }
+
+    static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
+        WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
+                sRunningTransitions.get();
+        if (runningTransitions == null || runningTransitions.get() == null) {
+            ArrayMap<ViewGroup, ArrayList<Transition>> transitions = new ArrayMap<>();
+            runningTransitions = new WeakReference<>(transitions);
+            sRunningTransitions.set(runningTransitions);
+        }
+        return runningTransitions.get();
+    }
+
+    private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
+            final Transition transition) {
+        if (transition != null && sceneRoot != null) {
+            MultiListener listener = new MultiListener(transition, sceneRoot);
+            sceneRoot.addOnAttachStateChangeListener(listener);
+            sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
+        }
+    }
+
+    /**
+     * This private utility class is used to listen for both OnPreDraw and
+     * OnAttachStateChange events. OnPreDraw events are the main ones we care
+     * about since that's what triggers the transition to take place.
+     * OnAttachStateChange events are also important in case the view is removed
+     * from the hierarchy before the OnPreDraw event takes place; it's used to
+     * clean up things since the OnPreDraw listener didn't get called in time.
+     */
+    private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
+            View.OnAttachStateChangeListener {
+
+        Transition mTransition;
+
+        ViewGroup mSceneRoot;
+
+        MultiListener(Transition transition, ViewGroup sceneRoot) {
+            mTransition = transition;
+            mSceneRoot = sceneRoot;
+        }
+
+        private void removeListeners() {
+            mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+            mSceneRoot.removeOnAttachStateChangeListener(this);
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            removeListeners();
+
+            sPendingTransitions.remove(mSceneRoot);
+            ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
+            if (runningTransitions != null && runningTransitions.size() > 0) {
+                for (Transition runningTransition : runningTransitions) {
+                    runningTransition.resume(mSceneRoot);
+                }
+            }
+            mTransition.clearValues(true);
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            removeListeners();
+
+            // Don't start the transition if it's no longer pending.
+            if (!sPendingTransitions.remove(mSceneRoot)) {
+                return true;
+            }
+
+            // Add to running list, handle end to remove it
+            final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
+                    getRunningTransitions();
+            ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
+            ArrayList<Transition> previousRunningTransitions = null;
+            if (currentTransitions == null) {
+                currentTransitions = new ArrayList<>();
+                runningTransitions.put(mSceneRoot, currentTransitions);
+            } else if (currentTransitions.size() > 0) {
+                previousRunningTransitions = new ArrayList<>(currentTransitions);
+            }
+            currentTransitions.add(mTransition);
+            mTransition.addListener(new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(@NonNull Transition transition) {
+                    ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
+                    currentTransitions.remove(transition);
+                }
+            });
+            mTransition.captureValues(mSceneRoot, false);
+            if (previousRunningTransitions != null) {
+                for (Transition runningTransition : previousRunningTransitions) {
+                    runningTransition.resume(mSceneRoot);
+                }
+            }
+            mTransition.playTransition(mSceneRoot);
+
+            return true;
+        }
+    }
+
+    private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
+        // Capture current values
+        ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
+
+        if (runningTransitions != null && runningTransitions.size() > 0) {
+            for (Transition runningTransition : runningTransitions) {
+                runningTransition.pause(sceneRoot);
+            }
+        }
+
+        if (transition != null) {
+            transition.captureValues(sceneRoot, true);
+        }
+
+        // Notify previous scene that it is being exited
+        Scene previousScene = Scene.getCurrentScene(sceneRoot);
+        if (previousScene != null) {
+            previousScene.exit();
+        }
+    }
+
+    /**
+     * Change to the given scene, using the
+     * appropriate transition for this particular scene change
+     * (as specified to the TransitionManager, or the default
+     * if no such transition exists).
+     *
+     * @param scene The Scene to change to
+     */
+    public void transitionTo(@NonNull Scene scene) {
+        // Auto transition if there is no transition declared for the Scene, but there is
+        // a root or parent view
+        changeScene(scene, getTransition(scene));
+    }
+
+    /**
+     * Convenience method to simply change to the given scene using
+     * the default transition for TransitionManager.
+     *
+     * @param scene The Scene to change to
+     */
+    public static void go(@NonNull Scene scene) {
+        changeScene(scene, sDefaultTransition);
+    }
+
+    /**
+     * Convenience method to simply change to the given scene using
+     * the given transition.
+     *
+     * <p>Passing in <code>null</code> for the transition parameter will
+     * result in the scene changing without any transition running, and is
+     * equivalent to calling {@link Scene#exit()} on the scene root's
+     * current scene, followed by {@link Scene#enter()} on the scene
+     * specified by the <code>scene</code> parameter.</p>
+     *
+     * @param scene      The Scene to change to
+     * @param transition The transition to use for this scene change. A
+     *                   value of null causes the scene change to happen with no transition.
+     */
+    public static void go(@NonNull Scene scene, @Nullable Transition transition) {
+        changeScene(scene, transition);
+    }
+
+    /**
+     * Convenience method to animate, using the default transition,
+     * to a new scene defined by all changes within the given scene root between
+     * calling this method and the next rendering frame.
+     * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
+     * with a value of <code>null</code> for the <code>transition</code> parameter.
+     *
+     * @param sceneRoot The root of the View hierarchy to run the transition on.
+     */
+    public static void beginDelayedTransition(@NonNull final ViewGroup sceneRoot) {
+        beginDelayedTransition(sceneRoot, null);
+    }
+
+    /**
+     * Convenience method to animate to a new scene defined by all changes within
+     * the given scene root between calling this method and the next rendering frame.
+     * Calling this method causes TransitionManager to capture current values in the
+     * scene root and then post a request to run a transition on the next frame.
+     * At that time, the new values in the scene root will be captured and changes
+     * will be animated. There is no need to create a Scene; it is implied by
+     * changes which take place between calling this method and the next frame when
+     * the transition begins.
+     *
+     * <p>Calling this method several times before the next frame (for example, if
+     * unrelated code also wants to make dynamic changes and run a transition on
+     * the same scene root), only the first call will trigger capturing values
+     * and exiting the current scene. Subsequent calls to the method with the
+     * same scene root during the same frame will be ignored.</p>
+     *
+     * <p>Passing in <code>null</code> for the transition parameter will
+     * cause the TransitionManager to use its default transition.</p>
+     *
+     * @param sceneRoot  The root of the View hierarchy to run the transition on.
+     * @param transition The transition to use for this change. A
+     *                   value of null causes the TransitionManager to use the default transition.
+     */
+    public static void beginDelayedTransition(@NonNull final ViewGroup sceneRoot,
+            @Nullable Transition transition) {
+        if (!sPendingTransitions.contains(sceneRoot) && ViewCompat.isLaidOut(sceneRoot)) {
+            if (Transition.DBG) {
+                Log.d(LOG_TAG, "beginDelayedTransition: root, transition = "
+                        + sceneRoot + ", " + transition);
+            }
+            sPendingTransitions.add(sceneRoot);
+            if (transition == null) {
+                transition = sDefaultTransition;
+            }
+            final Transition transitionClone = transition.clone();
+            sceneChangeSetup(sceneRoot, transitionClone);
+            Scene.setCurrentScene(sceneRoot, null);
+            sceneChangeRunTransition(sceneRoot, transitionClone);
+        }
+    }
+
+    /**
+     * Ends all pending and ongoing transitions on the specified scene root.
+     *
+     * @param sceneRoot The root of the View hierarchy to end transitions on.
+     */
+    public static void endTransitions(final ViewGroup sceneRoot) {
+        sPendingTransitions.remove(sceneRoot);
+        final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
+        if (runningTransitions != null && !runningTransitions.isEmpty()) {
+            // Make a copy in case this is called by an onTransitionEnd listener
+            ArrayList<Transition> copy = new ArrayList<>(runningTransitions);
+            for (int i = copy.size() - 1; i >= 0; i--) {
+                final Transition transition = copy.get(i);
+                transition.forceToEnd(sceneRoot);
+            }
+        }
+    }
+
+}
diff --git a/androidx/transition/TransitionManagerTest.java b/androidx/transition/TransitionManagerTest.java
new file mode 100644
index 0000000..87b490a
--- /dev/null
+++ b/androidx/transition/TransitionManagerTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 androidx.transition;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.ViewGroup;
+
+import androidx.transition.test.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@MediumTest
+public class TransitionManagerTest extends BaseTest {
+
+    private Scene[] mScenes = new Scene[2];
+
+    @Before
+    public void prepareScenes() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        mScenes[0] = Scene.getSceneForLayout(root, R.layout.support_scene0, activity);
+        mScenes[1] = Scene.getSceneForLayout(root, R.layout.support_scene1, activity);
+    }
+
+    @Test
+    public void testSetup() {
+        assertThat(mScenes[0], is(notNullValue()));
+        assertThat(mScenes[1], is(notNullValue()));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testGo_enterAction() {
+        CheckCalledRunnable runnable = new CheckCalledRunnable();
+        mScenes[0].setEnterAction(runnable);
+        assertThat(runnable.wasCalled(), is(false));
+        TransitionManager.go(mScenes[0]);
+        assertThat(runnable.wasCalled(), is(true));
+    }
+
+    @Test
+    public void testGo_exitAction() throws Throwable {
+        final CheckCalledRunnable enter = new CheckCalledRunnable();
+        final CheckCalledRunnable exit = new CheckCalledRunnable();
+        mScenes[0].setEnterAction(enter);
+        mScenes[0].setExitAction(exit);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                assertThat(enter.wasCalled(), is(false));
+                assertThat(exit.wasCalled(), is(false));
+                TransitionManager.go(mScenes[0]);
+                assertThat(enter.wasCalled(), is(true));
+                assertThat(exit.wasCalled(), is(false));
+            }
+        });
+        // Let the main thread catch up with the scene change
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(mScenes[1]);
+                assertThat(exit.wasCalled(), is(true));
+            }
+        });
+    }
+
+    @Test
+    public void testGo_transitionListenerStart() throws Throwable {
+        final SyncTransitionListener listener =
+                new SyncTransitionListener(SyncTransitionListener.EVENT_START);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Transition transition = new AutoTransition();
+                transition.setDuration(0);
+                assertThat(transition.addListener(listener), is(sameInstance(transition)));
+                TransitionManager.go(mScenes[0], transition);
+            }
+        });
+        assertThat("Timed out waiting for the TransitionListener",
+                listener.await(), is(true));
+    }
+
+    @Test
+    public void testGo_transitionListenerEnd() throws Throwable {
+        final SyncTransitionListener listener =
+                new SyncTransitionListener(SyncTransitionListener.EVENT_END);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                Transition transition = new AutoTransition();
+                transition.setDuration(0);
+                assertThat(transition.addListener(listener), is(sameInstance(transition)));
+                TransitionManager.go(mScenes[0], transition);
+            }
+        });
+        assertThat("Timed out waiting for the TransitionListener",
+                listener.await(), is(true));
+    }
+
+    @Test
+    public void testGo_nullParameter() throws Throwable {
+        final ViewGroup root = rule.getActivity().getRoot();
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(mScenes[0], null);
+                assertThat(Scene.getCurrentScene(root), is(mScenes[0]));
+                TransitionManager.go(mScenes[1], null);
+                assertThat(Scene.getCurrentScene(root), is(mScenes[1]));
+            }
+        });
+    }
+
+    @Test
+    public void testEndTransitions() throws Throwable {
+        final ViewGroup root = rule.getActivity().getRoot();
+        final Transition transition = new AutoTransition();
+        // This transition is very long, but will be forced to end as soon as it starts
+        transition.setDuration(30000);
+        final Transition.TransitionListener listener = mock(Transition.TransitionListener.class);
+        transition.addListener(listener);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(mScenes[0], transition);
+            }
+        });
+        verify(listener, timeout(3000)).onTransitionStart(any(Transition.class));
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.endTransitions(root);
+            }
+        });
+        verify(listener, timeout(3000)).onTransitionEnd(any(Transition.class));
+    }
+
+    @Test
+    public void testEndTransitionsBeforeStarted() throws Throwable {
+        final ViewGroup root = rule.getActivity().getRoot();
+        final Transition transition = new AutoTransition();
+        transition.setDuration(0);
+        final Transition.TransitionListener listener = mock(Transition.TransitionListener.class);
+        transition.addListener(listener);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(mScenes[0], transition);
+                // This terminates the transition before it starts
+                TransitionManager.endTransitions(root);
+            }
+        });
+        verify(listener, never()).onTransitionStart(any(Transition.class));
+        verify(listener, never()).onTransitionEnd(any(Transition.class));
+    }
+
+}
diff --git a/androidx/transition/TransitionPropagation.java b/androidx/transition/TransitionPropagation.java
new file mode 100644
index 0000000..004aee5
--- /dev/null
+++ b/androidx/transition/TransitionPropagation.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.view.ViewGroup;
+
+/**
+ * Extend <code>TransitionPropagation</code> to customize start delays for Animators created
+ * in {@link Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}.
+ * A Transition such as {@link Explode} defaults to using {@link CircularPropagation} and Views
+ * closer to the epicenter will move out of the scene later and into the scene sooner than Views
+ * farther from the epicenter, giving the appearance of inertia. With no TransitionPropagation, all
+ * Views will react simultaneously to the start of the transition.
+ *
+ * @see Transition#setPropagation(TransitionPropagation)
+ * @see Transition#getEpicenter()
+ */
+public abstract class TransitionPropagation {
+
+    /**
+     * Called by Transition to alter the Animator start delay. All start delays will be adjusted
+     * such that the minimum becomes zero.
+     *
+     * @param sceneRoot   The root of the View hierarchy running the transition.
+     * @param transition  The transition that created the Animator
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues   The values for the target in the end scene.
+     * @return A start delay to use with the Animator created by <code>transition</code>. The
+     * delay will be offset by the minimum delay of all <code>TransitionPropagation</code>s
+     * used in the Transition so that the smallest delay will be 0. Returned values may be
+     * negative.
+     */
+    public abstract long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues);
+
+    /**
+     * Captures the values in the start or end scene for the properties that this
+     * transition propagation monitors. These values are then passed as the startValues
+     * or endValues structure in a later call to
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * method to determine the start delay.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     *                         wishes to store. Values are stored in the <code>values</code> field
+     *                         of this TransitionValues object and are keyed from
+     *                         a String value. For example, to store a view's rotation value,
+     *                         a transition might call
+     *                         <code>transitionValues.values.put("appname:transitionname:rotation",
+     *                         view.getRotation())</code>. The target view will already be stored
+     *                         in
+     *                         the transitionValues structure when this method is called.
+     */
+    public abstract void captureValues(TransitionValues transitionValues);
+
+    /**
+     * Returns the set of property names stored in the {@link TransitionValues}
+     * object passed into {@link #captureValues(TransitionValues)} that
+     * this transition propagation cares about for the purposes of preventing
+     * duplicate capturing of property values.
+     *
+     * <p>A <code>TransitionPropagation</code> must override this method to prevent
+     * duplicate capturing of values and must contain at least one </p>
+     *
+     * @return An array of property names as described in the class documentation for
+     * {@link TransitionValues}.
+     */
+    public abstract String[] getPropagationProperties();
+
+}
diff --git a/androidx/transition/TransitionSet.java b/androidx/transition/TransitionSet.java
new file mode 100644
index 0000000..bf945fd
--- /dev/null
+++ b/androidx/transition/TransitionSet.java
@@ -0,0 +1,602 @@
+/*
+ * 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AndroidRuntimeException;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.content.res.TypedArrayUtils;
+
+import java.util.ArrayList;
+
+/**
+ * A TransitionSet is a parent of child transitions (including other
+ * TransitionSets). Using TransitionSets enables more complex
+ * choreography of transitions, where some sets play {@link #ORDERING_TOGETHER} and
+ * others play {@link #ORDERING_SEQUENTIAL}. For example, {@link AutoTransition}
+ * uses a TransitionSet to sequentially play a Fade(Fade.OUT), followed by
+ * a {@link ChangeBounds}, followed by a Fade(Fade.OUT) transition.
+ *
+ * <p>A TransitionSet can be described in a resource file by using the
+ * tag <code>transitionSet</code>, along with the standard
+ * attributes of {@code TransitionSet} and {@link Transition}. Child transitions of the
+ * TransitionSet object can be loaded by adding those child tags inside the
+ * enclosing <code>transitionSet</code> tag. For example, the following xml
+ * describes a TransitionSet that plays a Fade and then a ChangeBounds
+ * transition on the affected view targets:</p>
+ * <pre>
+ *     &lt;transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ *             android:transitionOrdering="sequential"&gt;
+ *         &lt;fade/&gt;
+ *         &lt;changeBounds/&gt;
+ *     &lt;/transitionSet&gt;
+ * </pre>
+ */
+public class TransitionSet extends Transition {
+
+    private ArrayList<Transition> mTransitions = new ArrayList<>();
+    private boolean mPlayTogether = true;
+    private int mCurrentListeners;
+    private boolean mStarted = false;
+
+    /**
+     * A flag used to indicate that the child transitions of this set
+     * should all start at the same time.
+     */
+    public static final int ORDERING_TOGETHER = 0;
+
+    /**
+     * A flag used to indicate that the child transitions of this set should
+     * play in sequence; when one child transition ends, the next child
+     * transition begins. Note that a transition does not end until all
+     * instances of it (which are playing on all applicable targets of the
+     * transition) end.
+     */
+    public static final int ORDERING_SEQUENTIAL = 1;
+
+    /**
+     * Constructs an empty transition set. Add child transitions to the
+     * set by calling {@link #addTransition(Transition)} )}. By default,
+     * child transitions will play {@link #ORDERING_TOGETHER together}.
+     */
+    public TransitionSet() {
+    }
+
+    public TransitionSet(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.TRANSITION_SET);
+        int ordering = TypedArrayUtils.getNamedInt(a, (XmlResourceParser) attrs,
+                "transitionOrdering", Styleable.TransitionSet.TRANSITION_ORDERING,
+                TransitionSet.ORDERING_TOGETHER);
+        setOrdering(ordering);
+        a.recycle();
+    }
+
+    /**
+     * Sets the play order of this set's child transitions.
+     *
+     * @param ordering {@link #ORDERING_TOGETHER} to play this set's child
+     *                 transitions together, {@link #ORDERING_SEQUENTIAL} to play the child
+     *                 transitions in sequence.
+     * @return This transitionSet object.
+     */
+    @NonNull
+    public TransitionSet setOrdering(int ordering) {
+        switch (ordering) {
+            case ORDERING_SEQUENTIAL:
+                mPlayTogether = false;
+                break;
+            case ORDERING_TOGETHER:
+                mPlayTogether = true;
+                break;
+            default:
+                throw new AndroidRuntimeException("Invalid parameter for TransitionSet "
+                        + "ordering: " + ordering);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the ordering of this TransitionSet. By default, the value is
+     * {@link #ORDERING_TOGETHER}.
+     *
+     * @return {@link #ORDERING_TOGETHER} if child transitions will play at the same
+     * time, {@link #ORDERING_SEQUENTIAL} if they will play in sequence.
+     * @see #setOrdering(int)
+     */
+    public int getOrdering() {
+        return mPlayTogether ? ORDERING_TOGETHER : ORDERING_SEQUENTIAL;
+    }
+
+    /**
+     * Adds child transition to this set. The order in which this child transition
+     * is added relative to other child transitions that are added, in addition to
+     * the {@link #getOrdering() ordering} property, determines the
+     * order in which the transitions are started.
+     *
+     * <p>If this transitionSet has a {@link #getDuration() duration} set on it, the
+     * child transition will inherit that duration. Transitions are assumed to have
+     * a maximum of one transitionSet parent.</p>
+     *
+     * @param transition A non-null child transition to be added to this set.
+     * @return This transitionSet object.
+     */
+    @NonNull
+    public TransitionSet addTransition(@NonNull Transition transition) {
+        mTransitions.add(transition);
+        transition.mParent = this;
+        if (mDuration >= 0) {
+            transition.setDuration(mDuration);
+        }
+        return this;
+    }
+
+    /**
+     * Returns the number of child transitions in the TransitionSet.
+     *
+     * @return The number of child transitions in the TransitionSet.
+     * @see #addTransition(Transition)
+     * @see #getTransitionAt(int)
+     */
+    public int getTransitionCount() {
+        return mTransitions.size();
+    }
+
+    /**
+     * Returns the child Transition at the specified position in the TransitionSet.
+     *
+     * @param index The position of the Transition to retrieve.
+     * @see #addTransition(Transition)
+     * @see #getTransitionCount()
+     */
+    public Transition getTransitionAt(int index) {
+        if (index < 0 || index >= mTransitions.size()) {
+            return null;
+        }
+        return mTransitions.get(index);
+    }
+
+    /**
+     * Setting a non-negative duration on a TransitionSet causes all of the child
+     * transitions (current and future) to inherit this duration.
+     *
+     * @param duration The length of the animation, in milliseconds.
+     * @return This transitionSet object.
+     */
+    @NonNull
+    @Override
+    public TransitionSet setDuration(long duration) {
+        super.setDuration(duration);
+        if (mDuration >= 0) {
+            int numTransitions = mTransitions.size();
+            for (int i = 0; i < numTransitions; ++i) {
+                mTransitions.get(i).setDuration(duration);
+            }
+        }
+        return this;
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet setStartDelay(long startDelay) {
+        return (TransitionSet) super.setStartDelay(startDelay);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet setInterpolator(@Nullable TimeInterpolator interpolator) {
+        return (TransitionSet) super.setInterpolator(interpolator);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet addTarget(@NonNull View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(target);
+        }
+        return (TransitionSet) super.addTarget(target);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet addTarget(@IdRes int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetId);
+        }
+        return (TransitionSet) super.addTarget(targetId);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet addTarget(@NonNull String targetName) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetName);
+        }
+        return (TransitionSet) super.addTarget(targetName);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet addTarget(@NonNull Class targetType) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetType);
+        }
+        return (TransitionSet) super.addTarget(targetType);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet addListener(@NonNull TransitionListener listener) {
+        return (TransitionSet) super.addListener(listener);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet removeTarget(@IdRes int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(targetId);
+        }
+        return (TransitionSet) super.removeTarget(targetId);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet removeTarget(@NonNull View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet removeTarget(@NonNull Class target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet removeTarget(@NonNull String target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @NonNull
+    @Override
+    public Transition excludeTarget(@NonNull View target, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(target, exclude);
+        }
+        return super.excludeTarget(target, exclude);
+    }
+
+    @NonNull
+    @Override
+    public Transition excludeTarget(@NonNull String targetName, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetName, exclude);
+        }
+        return super.excludeTarget(targetName, exclude);
+    }
+
+    @NonNull
+    @Override
+    public Transition excludeTarget(int targetId, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetId, exclude);
+        }
+        return super.excludeTarget(targetId, exclude);
+    }
+
+    @NonNull
+    @Override
+    public Transition excludeTarget(@NonNull Class type, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(type, exclude);
+        }
+        return super.excludeTarget(type, exclude);
+    }
+
+    @NonNull
+    @Override
+    public TransitionSet removeListener(@NonNull TransitionListener listener) {
+        return (TransitionSet) super.removeListener(listener);
+    }
+
+    @Override
+    public void setPathMotion(PathMotion pathMotion) {
+        super.setPathMotion(pathMotion);
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).setPathMotion(pathMotion);
+        }
+    }
+
+    /**
+     * Removes the specified child transition from this set.
+     *
+     * @param transition The transition to be removed.
+     * @return This transitionSet object.
+     */
+    @NonNull
+    public TransitionSet removeTransition(@NonNull Transition transition) {
+        mTransitions.remove(transition);
+        transition.mParent = null;
+        return this;
+    }
+
+    /**
+     * Sets up listeners for each of the child transitions. This is used to
+     * determine when this transition set is finished (all child transitions
+     * must finish first).
+     */
+    private void setupStartEndListeners() {
+        TransitionSetListener listener = new TransitionSetListener(this);
+        for (Transition childTransition : mTransitions) {
+            childTransition.addListener(listener);
+        }
+        mCurrentListeners = mTransitions.size();
+    }
+
+    /**
+     * This listener is used to detect when all child transitions are done, at
+     * which point this transition set is also done.
+     */
+    static class TransitionSetListener extends TransitionListenerAdapter {
+
+        TransitionSet mTransitionSet;
+
+        TransitionSetListener(TransitionSet transitionSet) {
+            mTransitionSet = transitionSet;
+        }
+
+        @Override
+        public void onTransitionStart(@NonNull Transition transition) {
+            if (!mTransitionSet.mStarted) {
+                mTransitionSet.start();
+                mTransitionSet.mStarted = true;
+            }
+        }
+
+        @Override
+        public void onTransitionEnd(@NonNull Transition transition) {
+            --mTransitionSet.mCurrentListeners;
+            if (mTransitionSet.mCurrentListeners == 0) {
+                // All child trans
+                mTransitionSet.mStarted = false;
+                mTransitionSet.end();
+            }
+            transition.removeListener(this);
+        }
+
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
+            ArrayList<TransitionValues> endValuesList) {
+        long startDelay = getStartDelay();
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; i++) {
+            Transition childTransition = mTransitions.get(i);
+            // We only set the start delay on the first transition if we are playing
+            // the transitions sequentially.
+            if (startDelay > 0 && (mPlayTogether || i == 0)) {
+                long childStartDelay = childTransition.getStartDelay();
+                if (childStartDelay > 0) {
+                    childTransition.setStartDelay(startDelay + childStartDelay);
+                } else {
+                    childTransition.setStartDelay(startDelay);
+                }
+            }
+            childTransition.createAnimators(sceneRoot, startValues, endValues, startValuesList,
+                    endValuesList);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    protected void runAnimators() {
+        if (mTransitions.isEmpty()) {
+            start();
+            end();
+            return;
+        }
+        setupStartEndListeners();
+        if (!mPlayTogether) {
+            // Setup sequence with listeners
+            // TODO: Need to add listeners in such a way that we can remove them later if canceled
+            for (int i = 1; i < mTransitions.size(); ++i) {
+                Transition previousTransition = mTransitions.get(i - 1);
+                final Transition nextTransition = mTransitions.get(i);
+                previousTransition.addListener(new TransitionListenerAdapter() {
+                    @Override
+                    public void onTransitionEnd(@NonNull Transition transition) {
+                        nextTransition.runAnimators();
+                        transition.removeListener(this);
+                    }
+                });
+            }
+            Transition firstTransition = mTransitions.get(0);
+            if (firstTransition != null) {
+                firstTransition.runAnimators();
+            }
+        } else {
+            for (Transition childTransition : mTransitions) {
+                childTransition.runAnimators();
+            }
+        }
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        if (isValidTarget(transitionValues.view)) {
+            for (Transition childTransition : mTransitions) {
+                if (childTransition.isValidTarget(transitionValues.view)) {
+                    childTransition.captureStartValues(transitionValues);
+                    transitionValues.mTargetedTransitions.add(childTransition);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        if (isValidTarget(transitionValues.view)) {
+            for (Transition childTransition : mTransitions) {
+                if (childTransition.isValidTarget(transitionValues.view)) {
+                    childTransition.captureEndValues(transitionValues);
+                    transitionValues.mTargetedTransitions.add(childTransition);
+                }
+            }
+        }
+    }
+
+    @Override
+    void capturePropagationValues(TransitionValues transitionValues) {
+        super.capturePropagationValues(transitionValues);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).capturePropagationValues(transitionValues);
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void pause(View sceneRoot) {
+        super.pause(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).pause(sceneRoot);
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    public void resume(View sceneRoot) {
+        super.resume(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).resume(sceneRoot);
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    protected void cancel() {
+        super.cancel();
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).cancel();
+        }
+    }
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @Override
+    void forceToEnd(ViewGroup sceneRoot) {
+        super.forceToEnd(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).forceToEnd(sceneRoot);
+        }
+    }
+
+    @Override
+    TransitionSet setSceneRoot(ViewGroup sceneRoot) {
+        super.setSceneRoot(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setSceneRoot(sceneRoot);
+        }
+        return this;
+    }
+
+    @Override
+    void setCanRemoveViews(boolean canRemoveViews) {
+        super.setCanRemoveViews(canRemoveViews);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setCanRemoveViews(canRemoveViews);
+        }
+    }
+
+    @Override
+    public void setPropagation(TransitionPropagation propagation) {
+        super.setPropagation(propagation);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setPropagation(propagation);
+        }
+    }
+
+    @Override
+    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+        super.setEpicenterCallback(epicenterCallback);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setEpicenterCallback(epicenterCallback);
+        }
+    }
+
+    @Override
+    String toString(String indent) {
+        String result = super.toString(indent);
+        for (int i = 0; i < mTransitions.size(); ++i) {
+            result += "\n" + mTransitions.get(i).toString(indent + "  ");
+        }
+        return result;
+    }
+
+    @Override
+    public Transition clone() {
+        TransitionSet clone = (TransitionSet) super.clone();
+        clone.mTransitions = new ArrayList<>();
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            clone.addTransition(mTransitions.get(i).clone());
+        }
+        return clone;
+    }
+
+}
diff --git a/androidx/transition/TransitionSetTest.java b/androidx/transition/TransitionSetTest.java
new file mode 100644
index 0000000..194d227
--- /dev/null
+++ b/androidx/transition/TransitionSetTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.sameInstance;
+
+import android.support.test.filters.MediumTest;
+import android.view.View;
+
+import androidx.transition.test.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@MediumTest
+public class TransitionSetTest extends BaseTest {
+
+    private final TransitionSet mTransitionSet = new TransitionSet();
+    private final Transition mTransition = new TransitionTest.EmptyTransition();
+
+    @Before
+    public void setUp() {
+        // mTransitionSet has 1 item from the start
+        mTransitionSet.addTransition(mTransition);
+    }
+
+    @Test
+    public void testOrdering() {
+        assertThat(mTransitionSet.getOrdering(), is(TransitionSet.ORDERING_TOGETHER));
+        assertThat(mTransitionSet.setOrdering(TransitionSet.ORDERING_SEQUENTIAL),
+                is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getOrdering(), is(TransitionSet.ORDERING_SEQUENTIAL));
+    }
+
+    @Test
+    public void testAddAndRemoveTransition() {
+        assertThat(mTransitionSet.getTransitionCount(), is(1));
+        assertThat(mTransitionSet.getTransitionAt(0), is(sameInstance(mTransition)));
+        Transition anotherTransition = new TransitionTest.EmptyTransition();
+        assertThat(mTransitionSet.addTransition(anotherTransition),
+                is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTransitionCount(), is(2));
+        assertThat(mTransitionSet.getTransitionAt(0), is(sameInstance(mTransition)));
+        assertThat(mTransitionSet.getTransitionAt(1), is(sameInstance(anotherTransition)));
+        assertThat(mTransitionSet.removeTransition(mTransition),
+                is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTransitionCount(), is(1));
+    }
+
+    @Test
+    public void testSetDuration() {
+        assertThat(mTransitionSet.setDuration(123), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getDuration(), is(123L));
+        assertThat(mTransition.getDuration(), is(123L));
+    }
+
+    @Test
+    public void testTargetId() {
+        assertThat(mTransitionSet.addTarget(R.id.view0), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetIds(), hasItem(R.id.view0));
+        assertThat(mTransitionSet.getTargetIds(), hasSize(1));
+        assertThat(mTransition.getTargetIds(), hasItem(R.id.view0));
+        assertThat(mTransition.getTargetIds(), hasSize(1));
+        assertThat(mTransitionSet.removeTarget(R.id.view0), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetIds(), hasSize(0));
+        assertThat(mTransition.getTargetIds(), hasSize(0));
+    }
+
+    @Test
+    public void testTargetView() {
+        final View view = new View(rule.getActivity());
+        assertThat(mTransitionSet.addTarget(view), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargets(), hasItem(view));
+        assertThat(mTransitionSet.getTargets(), hasSize(1));
+        assertThat(mTransition.getTargets(), hasItem(view));
+        assertThat(mTransition.getTargets(), hasSize(1));
+        assertThat(mTransitionSet.removeTarget(view), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargets(), hasSize(0));
+        assertThat(mTransition.getTargets(), hasSize(0));
+    }
+
+    @Test
+    public void testTargetName() {
+        assertThat(mTransitionSet.addTarget("abc"), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetNames(), hasItem("abc"));
+        assertThat(mTransitionSet.getTargetNames(), hasSize(1));
+        assertThat(mTransition.getTargetNames(), hasItem("abc"));
+        assertThat(mTransition.getTargetNames(), hasSize(1));
+        assertThat(mTransitionSet.removeTarget("abc"), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetNames(), hasSize(0));
+        assertThat(mTransition.getTargetNames(), hasSize(0));
+    }
+
+    @Test
+    public void testTargetClass() {
+        assertThat(mTransitionSet.addTarget(View.class), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetTypes(), hasItem(View.class));
+        assertThat(mTransitionSet.getTargetTypes(), hasSize(1));
+        assertThat(mTransition.getTargetTypes(), hasItem(View.class));
+        assertThat(mTransition.getTargetTypes(), hasSize(1));
+        assertThat(mTransitionSet.removeTarget(View.class), is(sameInstance(mTransitionSet)));
+        assertThat(mTransitionSet.getTargetTypes(), hasSize(0));
+        assertThat(mTransition.getTargetTypes(), hasSize(0));
+    }
+
+    @Test
+    public void testSetPropagation() {
+        final TransitionPropagation propagation = new SidePropagation();
+        mTransitionSet.setPropagation(propagation);
+        assertThat(mTransitionSet.getPropagation(), is(propagation));
+        assertThat(mTransition.getPropagation(), is(propagation));
+    }
+
+}
diff --git a/androidx/transition/TransitionTest.java b/androidx/transition/TransitionTest.java
new file mode 100644
index 0000000..85602e5
--- /dev/null
+++ b/androidx/transition/TransitionTest.java
@@ -0,0 +1,443 @@
+/*
+ * 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 androidx.transition;
+
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.sameInstance;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Rect;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.LinearInterpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+import androidx.transition.test.R;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+@MediumTest
+public class TransitionTest extends BaseTest {
+
+    private Scene[] mScenes = new Scene[2];
+    private View[] mViews = new View[3];
+
+    @Before
+    public void prepareScenes() {
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        mScenes[0] = Scene.getSceneForLayout(root, R.layout.support_scene0, activity);
+        mScenes[1] = Scene.getSceneForLayout(root, R.layout.support_scene1, activity);
+    }
+
+    @Test
+    public void testName() {
+        Transition transition = new EmptyTransition();
+        assertThat(transition.getName(),
+                is(equalTo("androidx.transition.TransitionTest$EmptyTransition")));
+    }
+
+    @Test
+    public void testDuration() {
+        Transition transition = new EmptyTransition();
+        long duration = 12345;
+        assertThat(transition.setDuration(duration), is(sameInstance(transition)));
+        assertThat(transition.getDuration(), is(duration));
+    }
+
+    @Test
+    public void testInterpolator() {
+        Transition transition = new EmptyTransition();
+        TimeInterpolator interpolator = new LinearInterpolator();
+        assertThat(transition.setInterpolator(interpolator), is(sameInstance(transition)));
+        assertThat(transition.getInterpolator(), is(interpolator));
+    }
+
+    @Test
+    public void testStartDelay() {
+        Transition transition = new EmptyTransition();
+        long startDelay = 12345;
+        assertThat(transition.setStartDelay(startDelay), is(sameInstance(transition)));
+        assertThat(transition.getStartDelay(), is(startDelay));
+    }
+
+    @Test
+    public void testTargetIds() {
+        Transition transition = new EmptyTransition();
+        assertThat(transition.addTarget(R.id.view0), is(sameInstance(transition)));
+        assertThat(transition.addTarget(R.id.view1), is(sameInstance(transition)));
+        List<Integer> targetIds = transition.getTargetIds();
+        assertThat(targetIds.size(), is(2));
+        assertThat(targetIds, hasItem(R.id.view0));
+        assertThat(targetIds, hasItem(R.id.view1));
+        assertThat(transition.removeTarget(R.id.view0), is(sameInstance(transition)));
+        targetIds = transition.getTargetIds();
+        assertThat(targetIds.size(), is(1));
+        assertThat(targetIds, not(hasItem(R.id.view0)));
+        assertThat(targetIds, hasItem(R.id.view1));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testTargetView() {
+        // Set up views
+        TransitionActivity activity = rule.getActivity();
+        ViewGroup root = activity.getRoot();
+        View container = LayoutInflater.from(activity)
+                .inflate(R.layout.support_scene0, root, false);
+        root.addView(container);
+        View view0 = container.findViewById(R.id.view0);
+        View view1 = container.findViewById(R.id.view1);
+        // Test transition targets
+        Transition transition = new EmptyTransition();
+        assertThat(transition.addTarget(view0), is(sameInstance(transition)));
+        assertThat(transition.addTarget(view1), is(sameInstance(transition)));
+        List<View> targets = transition.getTargets();
+        assertThat(targets.size(), is(2));
+        assertThat(targets, hasItem(sameInstance(view0)));
+        assertThat(targets, hasItem(sameInstance(view1)));
+        assertThat(transition.removeTarget(view0), is(sameInstance(transition)));
+        targets = transition.getTargets();
+        assertThat(targets.size(), is(1));
+        assertThat(targets, not(hasItem(sameInstance(view0))));
+        assertThat(targets, hasItem(sameInstance(view1)));
+    }
+
+    @Test
+    public void testTargetName() {
+        Transition transition = new EmptyTransition();
+        assertThat(transition.addTarget("a"), is(sameInstance(transition)));
+        assertThat(transition.addTarget("b"), is(sameInstance(transition)));
+        List<String> targetNames = transition.getTargetNames();
+        assertNotNull(targetNames);
+        assertThat(targetNames.size(), is(2));
+        assertThat(targetNames, hasItem("a"));
+        assertThat(targetNames, hasItem("b"));
+        transition.removeTarget("a");
+        assertThat(targetNames.size(), is(1));
+        assertThat(targetNames, not(hasItem("a")));
+        assertThat(targetNames, hasItem("b"));
+    }
+
+    @Test
+    public void testTargetType() {
+        Transition transition = new EmptyTransition();
+        assertThat(transition.addTarget(Button.class), is(sameInstance(transition)));
+        assertThat(transition.addTarget(ImageView.class), is(sameInstance(transition)));
+        List<Class> targetTypes = transition.getTargetTypes();
+        assertNotNull(targetTypes);
+        assertThat(targetTypes.size(), is(2));
+        assertThat(targetTypes, hasItem(Button.class));
+        assertThat(targetTypes, hasItem(ImageView.class));
+        transition.removeTarget(Button.class);
+        assertThat(targetTypes.size(), is(1));
+        assertThat(targetTypes, not(hasItem(Button.class)));
+        assertThat(targetTypes, hasItem(ImageView.class));
+    }
+
+    @Test
+    public void testExcludeTargetId() throws Throwable {
+        showInitialScene();
+        Transition transition = new EmptyTransition();
+        transition.addTarget(R.id.view0);
+        transition.addTarget(R.id.view1);
+        View view0 = rule.getActivity().findViewById(R.id.view0);
+        View view1 = rule.getActivity().findViewById(R.id.view1);
+        assertThat(transition.isValidTarget(view0), is(true));
+        assertThat(transition.isValidTarget(view1), is(true));
+        transition.excludeTarget(R.id.view0, true);
+        assertThat(transition.isValidTarget(view0), is(false));
+        assertThat(transition.isValidTarget(view1), is(true));
+    }
+
+    @Test
+    public void testExcludeTargetView() throws Throwable {
+        showInitialScene();
+        Transition transition = new EmptyTransition();
+        View view0 = rule.getActivity().findViewById(R.id.view0);
+        View view1 = rule.getActivity().findViewById(R.id.view1);
+        transition.addTarget(view0);
+        transition.addTarget(view1);
+        assertThat(transition.isValidTarget(view0), is(true));
+        assertThat(transition.isValidTarget(view1), is(true));
+        transition.excludeTarget(view0, true);
+        assertThat(transition.isValidTarget(view0), is(false));
+        assertThat(transition.isValidTarget(view1), is(true));
+    }
+
+    @Test
+    public void testExcludeTargetName() throws Throwable {
+        showInitialScene();
+        Transition transition = new EmptyTransition();
+        View view0 = rule.getActivity().findViewById(R.id.view0);
+        View view1 = rule.getActivity().findViewById(R.id.view1);
+        ViewCompat.setTransitionName(view0, "zero");
+        ViewCompat.setTransitionName(view1, "one");
+        transition.addTarget("zero");
+        transition.addTarget("one");
+        assertThat(transition.isValidTarget(view0), is(true));
+        assertThat(transition.isValidTarget(view1), is(true));
+        transition.excludeTarget("zero", true);
+        assertThat(transition.isValidTarget(view0), is(false));
+        assertThat(transition.isValidTarget(view1), is(true));
+    }
+
+    @Test
+    public void testExcludeTargetType() throws Throwable {
+        showInitialScene();
+        Transition transition = new EmptyTransition();
+        FrameLayout container = (FrameLayout) rule.getActivity().findViewById(R.id.container);
+        View view0 = rule.getActivity().findViewById(R.id.view0);
+        transition.addTarget(View.class);
+        assertThat(transition.isValidTarget(container), is(true));
+        assertThat(transition.isValidTarget(view0), is(true));
+        transition.excludeTarget(FrameLayout.class, true);
+        assertThat(transition.isValidTarget(container), is(false));
+        assertThat(transition.isValidTarget(view0), is(true));
+    }
+
+    @Test
+    public void testListener() {
+        Transition transition = new EmptyTransition();
+        Transition.TransitionListener listener = new EmptyTransitionListener();
+        assertThat(transition.addListener(listener), is(sameInstance(transition)));
+        assertThat(transition.removeListener(listener), is(sameInstance(transition)));
+    }
+
+    @Test
+    public void testMatchOrder() throws Throwable {
+        showInitialScene();
+        final Transition transition = new ChangeBounds() {
+            @Nullable
+            @Override
+            public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                    @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+                if (startValues != null && endValues != null) {
+                    fail("Match by View ID should be prevented");
+                }
+                return super.createAnimator(sceneRoot, startValues, endValues);
+            }
+        };
+        transition.setDuration(0);
+        // This prevents matches between start and end scenes because they have different set of
+        // View instances. They will be regarded as independent views even though they share the
+        // same View IDs.
+        transition.setMatchOrder(Transition.MATCH_INSTANCE);
+        SyncRunnable enter1 = new SyncRunnable();
+        mScenes[1].setEnterAction(enter1);
+        goToScene(mScenes[1], transition);
+        if (!enter1.await()) {
+            fail("Timed out while waiting for scene change");
+        }
+    }
+
+    @Test
+    public void testExcludedTransitionAnimator() throws Throwable {
+        showInitialScene();
+        final Animator.AnimatorListener animatorListener = mock(Animator.AnimatorListener.class);
+        final DummyTransition transition = new DummyTransition(animatorListener);
+        final SyncTransitionListener transitionListener = new SyncTransitionListener(
+                SyncTransitionListener.EVENT_END);
+        transition.addListener(transitionListener);
+        transition.addTarget(mViews[0]);
+        transition.excludeTarget(mViews[0], true);
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.beginDelayedTransition(rule.getActivity().getRoot(), transition);
+                mViews[0].setTranslationX(3.f);
+            }
+        });
+        if (!transitionListener.await()) {
+            fail("Timed out waiting for the TransitionListener");
+        }
+        verify(animatorListener, never()).onAnimationStart(any(Animator.class));
+    }
+
+    @Test
+    public void testEpicenter() throws Throwable {
+        final Transition transition = new EmptyTransition();
+        final Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() {
+            private Rect mRect = new Rect();
+
+            @Override
+            public Rect onGetEpicenter(@NonNull Transition t) {
+                assertThat(t, is(sameInstance(transition)));
+                mRect.set(1, 2, 3, 4);
+                return mRect;
+            }
+        };
+        transition.setEpicenterCallback(epicenterCallback);
+        assertThat(transition.getEpicenterCallback(),
+                is(sameInstance(transition.getEpicenterCallback())));
+        Rect rect = transition.getEpicenter();
+        assertNotNull(rect);
+        assertThat(rect.left, is(1));
+        assertThat(rect.top, is(2));
+        assertThat(rect.right, is(3));
+        assertThat(rect.bottom, is(4));
+    }
+
+    @Test
+    public void testSetPropagation() throws Throwable {
+        final Transition transition = new EmptyTransition();
+        assertThat(transition.getPropagation(), is(nullValue()));
+        final TransitionPropagation propagation = new CircularPropagation();
+        transition.setPropagation(propagation);
+        assertThat(propagation, is(sameInstance(propagation)));
+    }
+
+    @Test
+    public void testIsTransitionRequired() throws Throwable {
+        final EmptyTransition transition = new EmptyTransition();
+        assertThat(transition.isTransitionRequired(null, null), is(false));
+        final TransitionValues start = new TransitionValues();
+        final String propname = "android:transition:dummy";
+        start.values.put(propname, 1);
+        final TransitionValues end = new TransitionValues();
+        end.values.put(propname, 1);
+        assertThat(transition.isTransitionRequired(start, end), is(false));
+        end.values.put(propname, 2);
+        assertThat(transition.isTransitionRequired(start, end), is(true));
+    }
+
+    private void showInitialScene() throws Throwable {
+        SyncRunnable enter0 = new SyncRunnable();
+        mScenes[0].setEnterAction(enter0);
+        AutoTransition transition1 = new AutoTransition();
+        transition1.setDuration(0);
+        goToScene(mScenes[0], transition1);
+        if (!enter0.await()) {
+            fail("Timed out while waiting for scene change");
+        }
+        mViews[0] = rule.getActivity().findViewById(R.id.view0);
+        mViews[1] = rule.getActivity().findViewById(R.id.view1);
+        mViews[2] = rule.getActivity().findViewById(R.id.view2);
+    }
+
+    private void goToScene(final Scene scene, final Transition transition) throws Throwable {
+        rule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                TransitionManager.go(scene, transition);
+            }
+        });
+    }
+
+    public static class EmptyTransition extends Transition {
+
+        @Override
+        public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        }
+
+        @Override
+        public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        }
+
+        @Override
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                @Nullable TransitionValues startValues,
+                @Nullable TransitionValues endValues) {
+            return null;
+        }
+
+    }
+
+    public static class EmptyTransitionListener implements Transition.TransitionListener {
+
+        @Override
+        public void onTransitionStart(@NonNull Transition transition) {
+        }
+
+        @Override
+        public void onTransitionEnd(@NonNull Transition transition) {
+        }
+
+        @Override
+        public void onTransitionCancel(@NonNull Transition transition) {
+        }
+
+        @Override
+        public void onTransitionPause(@NonNull Transition transition) {
+        }
+
+        @Override
+        public void onTransitionResume(@NonNull Transition transition) {
+        }
+
+    }
+
+    /**
+     * A dummy transition for monitoring use of its animator by the Transition framework.
+     */
+    private static class DummyTransition extends Transition {
+
+        private final Animator.AnimatorListener mListener;
+
+        DummyTransition(Animator.AnimatorListener listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void captureStartValues(@NonNull TransitionValues transitionValues) {
+            transitionValues.values.put("state", 1);
+        }
+
+        @Override
+        public void captureEndValues(@NonNull TransitionValues transitionValues) {
+            transitionValues.values.put("state", 2);
+        }
+
+        @Override
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
+                TransitionValues endValues) {
+            if (startValues == null || endValues == null) {
+                return null;
+            }
+            final ObjectAnimator animator = ObjectAnimator
+                    .ofFloat(startValues.view, "translationX", 1.f, 2.f);
+            animator.addListener(mListener);
+            return animator;
+        }
+
+    }
+}
diff --git a/androidx/transition/TransitionUtils.java b/androidx/transition/TransitionUtils.java
new file mode 100644
index 0000000..39de677
--- /dev/null
+++ b/androidx/transition/TransitionUtils.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.TypeEvaluator;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+class TransitionUtils {
+
+    private static final int MAX_IMAGE_SIZE = 1024 * 1024;
+
+    /**
+     * Creates a View using the bitmap copy of <code>view</code>. If <code>view</code> is large,
+     * the copy will use a scaled bitmap of the given view.
+     *
+     * @param sceneRoot The ViewGroup in which the view copy will be displayed.
+     * @param view      The view to create a copy of.
+     * @param parent    The parent of view.
+     */
+    static View copyViewImage(ViewGroup sceneRoot, View view, View parent) {
+        Matrix matrix = new Matrix();
+        matrix.setTranslate(-parent.getScrollX(), -parent.getScrollY());
+        ViewUtils.transformMatrixToGlobal(view, matrix);
+        ViewUtils.transformMatrixToLocal(sceneRoot, matrix);
+        RectF bounds = new RectF(0, 0, view.getWidth(), view.getHeight());
+        matrix.mapRect(bounds);
+        int left = Math.round(bounds.left);
+        int top = Math.round(bounds.top);
+        int right = Math.round(bounds.right);
+        int bottom = Math.round(bounds.bottom);
+
+        ImageView copy = new ImageView(view.getContext());
+        copy.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        Bitmap bitmap = createViewBitmap(view, matrix, bounds);
+        if (bitmap != null) {
+            copy.setImageBitmap(bitmap);
+        }
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(right - left, View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(bottom - top, View.MeasureSpec.EXACTLY);
+        copy.measure(widthSpec, heightSpec);
+        copy.layout(left, top, right, bottom);
+        return copy;
+    }
+
+    /**
+     * Creates a Bitmap of the given view, using the Matrix matrix to transform to the local
+     * coordinates. <code>matrix</code> will be modified during the bitmap creation.
+     *
+     * <p>If the bitmap is large, it will be scaled uniformly down to at most 1MB size.</p>
+     *
+     * @param view   The view to create a bitmap for.
+     * @param matrix The matrix converting the view local coordinates to the coordinates that
+     *               the bitmap will be displayed in. <code>matrix</code> will be modified before
+     *               returning.
+     * @param bounds The bounds of the bitmap in the destination coordinate system (where the
+     *               view should be presented. Typically, this is matrix.mapRect(viewBounds);
+     * @return A bitmap of the given view or null if bounds has no width or height.
+     */
+    private static Bitmap createViewBitmap(View view, Matrix matrix, RectF bounds) {
+        Bitmap bitmap = null;
+        int bitmapWidth = Math.round(bounds.width());
+        int bitmapHeight = Math.round(bounds.height());
+        if (bitmapWidth > 0 && bitmapHeight > 0) {
+            float scale = Math.min(1f, ((float) MAX_IMAGE_SIZE) / (bitmapWidth * bitmapHeight));
+            bitmapWidth = (int) (bitmapWidth * scale);
+            bitmapHeight = (int) (bitmapHeight * scale);
+            matrix.postTranslate(-bounds.left, -bounds.top);
+            matrix.postScale(scale, scale);
+            bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+            Canvas canvas = new Canvas(bitmap);
+            canvas.concat(matrix);
+            view.draw(canvas);
+        }
+        return bitmap;
+    }
+
+    static Animator mergeAnimators(Animator animator1, Animator animator2) {
+        if (animator1 == null) {
+            return animator2;
+        } else if (animator2 == null) {
+            return animator1;
+        } else {
+            AnimatorSet animatorSet = new AnimatorSet();
+            animatorSet.playTogether(animator1, animator2);
+            return animatorSet;
+        }
+    }
+
+    static class MatrixEvaluator implements TypeEvaluator<Matrix> {
+
+        final float[] mTempStartValues = new float[9];
+
+        final float[] mTempEndValues = new float[9];
+
+        final Matrix mTempMatrix = new Matrix();
+
+        @Override
+        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+            startValue.getValues(mTempStartValues);
+            endValue.getValues(mTempEndValues);
+            for (int i = 0; i < 9; i++) {
+                float diff = mTempEndValues[i] - mTempStartValues[i];
+                mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
+            }
+            mTempMatrix.setValues(mTempEndValues);
+            return mTempMatrix;
+        }
+
+    }
+
+    private TransitionUtils() {
+    }
+}
diff --git a/androidx/transition/TransitionValues.java b/androidx/transition/TransitionValues.java
new file mode 100644
index 0000000..442bd15
--- /dev/null
+++ b/androidx/transition/TransitionValues.java
@@ -0,0 +1,88 @@
+/*
+ * 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 androidx.transition;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Data structure which holds cached values for the transition.
+ * The view field is the target which all of the values pertain to.
+ * The values field is a map which holds information for fields
+ * according to names selected by the transitions. These names should
+ * be unique to avoid clobbering values stored by other transitions,
+ * such as the convention project:transition_name:property_name. For
+ * example, the platform might store a property "alpha" in a transition
+ * "Fader" as "android:fader:alpha".
+ *
+ * <p>These values are cached during the
+ * {@link androidx.transition.Transition#captureStartValues(TransitionValues)}
+ * capture} phases of a scene change, once when the start values are captured
+ * and again when the end values are captured. These start/end values are then
+ * passed into the transitions via the
+ * for {@link androidx.transition.Transition#createAnimator(android.view.ViewGroup,
+ * TransitionValues, TransitionValues)} method.</p>
+ */
+public class TransitionValues {
+
+    /**
+     * The set of values tracked by transitions for this scene
+     */
+    public final Map<String, Object> values = new HashMap<>();
+
+    /**
+     * The View with these values
+     */
+    public View view;
+
+    /**
+     * The Transitions that targeted this view.
+     */
+    final ArrayList<Transition> mTargetedTransitions = new ArrayList<>();
+
+    @Override
+    public boolean equals(Object other) {
+        if (other instanceof TransitionValues) {
+            if (view == ((TransitionValues) other).view) {
+                if (values.equals(((TransitionValues) other).values)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * view.hashCode() + values.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        String returnValue = "TransitionValues@" + Integer.toHexString(hashCode()) + ":\n";
+        returnValue += "    view = " + view + "\n";
+        returnValue += "    values:";
+        for (String s : values.keySet()) {
+            returnValue += "    " + s + ": " + values.get(s) + "\n";
+        }
+        return returnValue;
+    }
+
+}
diff --git a/androidx/transition/TransitionValuesMaps.java b/androidx/transition/TransitionValuesMaps.java
new file mode 100644
index 0000000..4db4603
--- /dev/null
+++ b/androidx/transition/TransitionValuesMaps.java
@@ -0,0 +1,35 @@
+/*
+ * 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 androidx.transition;
+
+import android.util.SparseArray;
+import android.view.View;
+
+import androidx.collection.ArrayMap;
+import androidx.collection.LongSparseArray;
+
+class TransitionValuesMaps {
+
+    final ArrayMap<View, TransitionValues> mViewValues = new ArrayMap<>();
+
+    final SparseArray<View> mIdValues = new SparseArray<>();
+
+    final LongSparseArray<View> mItemIdValues = new LongSparseArray<>();
+
+    final ArrayMap<String, View> mNameValues = new ArrayMap<>();
+
+}
diff --git a/androidx/transition/TranslationAnimationCreator.java b/androidx/transition/TranslationAnimationCreator.java
new file mode 100644
index 0000000..c3e30e8
--- /dev/null
+++ b/androidx/transition/TranslationAnimationCreator.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.TimeInterpolator;
+import android.view.View;
+
+/**
+ * This class is used by Slide and Explode to create an animator that goes from the start
+ * position to the end position. It takes into account the canceled position so that it
+ * will not blink out or shift suddenly when the transition is interrupted.
+ */
+class TranslationAnimationCreator {
+
+    /**
+     * Creates an animator that can be used for x and/or y translations. When interrupted,
+     * it sets a tag to keep track of the position so that it may be continued from position.
+     *
+     * @param view         The view being moved. This may be in the overlay for onDisappear.
+     * @param values       The values containing the view in the view hierarchy.
+     * @param viewPosX     The x screen coordinate of view
+     * @param viewPosY     The y screen coordinate of view
+     * @param startX       The start translation x of view
+     * @param startY       The start translation y of view
+     * @param endX         The end translation x of view
+     * @param endY         The end translation y of view
+     * @param interpolator The interpolator to use with this animator.
+     * @return An animator that moves from (startX, startY) to (endX, endY) unless there was
+     * a previous interruption, in which case it moves from the current position to (endX, endY).
+     */
+    static Animator createAnimation(View view, TransitionValues values, int viewPosX, int viewPosY,
+            float startX, float startY, float endX, float endY, TimeInterpolator interpolator) {
+        float terminalX = view.getTranslationX();
+        float terminalY = view.getTranslationY();
+        int[] startPosition = (int[]) values.view.getTag(R.id.transition_position);
+        if (startPosition != null) {
+            startX = startPosition[0] - viewPosX + terminalX;
+            startY = startPosition[1] - viewPosY + terminalY;
+        }
+        // Initial position is at translation startX, startY, so position is offset by that amount
+        int startPosX = viewPosX + Math.round(startX - terminalX);
+        int startPosY = viewPosY + Math.round(startY - terminalY);
+
+        view.setTranslationX(startX);
+        view.setTranslationY(startY);
+        if (startX == endX && startY == endY) {
+            return null;
+        }
+        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(view,
+                PropertyValuesHolder.ofFloat(View.TRANSLATION_X, startX, endX),
+                PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, startY, endY));
+
+        TransitionPositionListener listener = new TransitionPositionListener(view, values.view,
+                startPosX, startPosY, terminalX, terminalY);
+        anim.addListener(listener);
+        AnimatorUtils.addPauseListener(anim, listener);
+        anim.setInterpolator(interpolator);
+        return anim;
+    }
+
+    private static class TransitionPositionListener extends AnimatorListenerAdapter {
+
+        private final View mViewInHierarchy;
+        private final View mMovingView;
+        private final int mStartX;
+        private final int mStartY;
+        private int[] mTransitionPosition;
+        private float mPausedX;
+        private float mPausedY;
+        private final float mTerminalX;
+        private final float mTerminalY;
+
+        private TransitionPositionListener(View movingView, View viewInHierarchy,
+                int startX, int startY, float terminalX, float terminalY) {
+            mMovingView = movingView;
+            mViewInHierarchy = viewInHierarchy;
+            mStartX = startX - Math.round(mMovingView.getTranslationX());
+            mStartY = startY - Math.round(mMovingView.getTranslationY());
+            mTerminalX = terminalX;
+            mTerminalY = terminalY;
+            mTransitionPosition = (int[]) mViewInHierarchy.getTag(R.id.transition_position);
+            if (mTransitionPosition != null) {
+                mViewInHierarchy.setTag(R.id.transition_position, null);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (mTransitionPosition == null) {
+                mTransitionPosition = new int[2];
+            }
+            mTransitionPosition[0] = Math.round(mStartX + mMovingView.getTranslationX());
+            mTransitionPosition[1] = Math.round(mStartY + mMovingView.getTranslationY());
+            mViewInHierarchy.setTag(R.id.transition_position, mTransitionPosition);
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedX = mMovingView.getTranslationX();
+            mPausedY = mMovingView.getTranslationY();
+            mMovingView.setTranslationX(mTerminalX);
+            mMovingView.setTranslationY(mTerminalY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mMovingView.setTranslationX(mPausedX);
+            mMovingView.setTranslationY(mPausedY);
+        }
+    }
+
+    private TranslationAnimationCreator() {
+    }
+}
diff --git a/androidx/transition/ViewGroupOverlayApi14.java b/androidx/transition/ViewGroupOverlayApi14.java
new file mode 100644
index 0000000..f1bd80a
--- /dev/null
+++ b/androidx/transition/ViewGroupOverlayApi14.java
@@ -0,0 +1,45 @@
+/*
+ * 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 androidx.transition;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+class ViewGroupOverlayApi14 extends ViewOverlayApi14 implements ViewGroupOverlayImpl {
+
+    ViewGroupOverlayApi14(Context context, ViewGroup hostView, View requestingView) {
+        super(context, hostView, requestingView);
+    }
+
+    static ViewGroupOverlayApi14 createFrom(ViewGroup viewGroup) {
+        return (ViewGroupOverlayApi14) ViewOverlayApi14.createFrom(viewGroup);
+    }
+
+    @Override
+    public void add(@NonNull View view) {
+        mOverlayViewGroup.add(view);
+    }
+
+    @Override
+    public void remove(@NonNull View view) {
+        mOverlayViewGroup.remove(view);
+    }
+
+}
diff --git a/androidx/transition/ViewGroupOverlayApi18.java b/androidx/transition/ViewGroupOverlayApi18.java
new file mode 100644
index 0000000..54e3413
--- /dev/null
+++ b/androidx/transition/ViewGroupOverlayApi18.java
@@ -0,0 +1,61 @@
+/*
+ * 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 androidx.transition;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(18)
+class ViewGroupOverlayApi18 implements ViewGroupOverlayImpl {
+
+    private final ViewGroupOverlay mViewGroupOverlay;
+
+    ViewGroupOverlayApi18(@NonNull ViewGroup group) {
+        mViewGroupOverlay = group.getOverlay();
+    }
+
+    @Override
+    public void add(@NonNull Drawable drawable) {
+        mViewGroupOverlay.add(drawable);
+    }
+
+    @Override
+    public void clear() {
+        mViewGroupOverlay.clear();
+    }
+
+    @Override
+    public void remove(@NonNull Drawable drawable) {
+        mViewGroupOverlay.remove(drawable);
+    }
+
+    @Override
+    public void add(@NonNull View view) {
+        mViewGroupOverlay.add(view);
+    }
+
+    @Override
+    public void remove(@NonNull View view) {
+        mViewGroupOverlay.remove(view);
+    }
+
+}
diff --git a/androidx/transition/ViewGroupOverlayImpl.java b/androidx/transition/ViewGroupOverlayImpl.java
new file mode 100644
index 0000000..be009e3
--- /dev/null
+++ b/androidx/transition/ViewGroupOverlayImpl.java
@@ -0,0 +1,59 @@
+/*
+ * 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 androidx.transition;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+interface ViewGroupOverlayImpl extends ViewOverlayImpl {
+
+    /**
+     * Adds a View to the overlay. The bounds of the added view should be
+     * relative to the host view. Any view added to the overlay should be
+     * removed when it is no longer needed or no longer visible.
+     *
+     * <p>Views in the overlay are visual-only; they do not receive input
+     * events and do not participate in focus traversal. Overlay views
+     * are intended to be transient, such as might be needed by a temporary
+     * animation effect.</p>
+     *
+     * <p>If the view has a parent, the view will be removed from that parent
+     * before being added to the overlay. Also, if that parent is attached
+     * in the current view hierarchy, the view will be repositioned
+     * such that it is in the same relative location inside the activity. For
+     * example, if the view's current parent lies 100 pixels to the right
+     * and 200 pixels down from the origin of the overlay's
+     * host view, then the view will be offset by (100, 200).</p>
+     *
+     * @param view The View to be added to the overlay. The added view will be
+     *             drawn when the overlay is drawn.
+     * @see #remove(View)
+     * @see android.view.ViewOverlay#add(android.graphics.drawable.Drawable)
+     */
+    void add(@NonNull View view);
+
+    /**
+     * Removes the specified View from the overlay.
+     *
+     * @param view The View to be removed from the overlay.
+     * @see #add(View)
+     * @see android.view.ViewOverlay#remove(android.graphics.drawable.Drawable)
+     */
+    void remove(@NonNull View view);
+
+}
diff --git a/androidx/transition/ViewGroupUtils.java b/androidx/transition/ViewGroupUtils.java
new file mode 100644
index 0000000..dee0fa9
--- /dev/null
+++ b/androidx/transition/ViewGroupUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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 androidx.transition;
+
+import android.os.Build;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Compatibility utilities for platform features of {@link ViewGroup}.
+ */
+class ViewGroupUtils {
+
+    /**
+     * Backward-compatible {@link ViewGroup#getOverlay()}.
+     */
+    static ViewGroupOverlayImpl getOverlay(@NonNull ViewGroup group) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new ViewGroupOverlayApi18(group);
+        }
+        return ViewGroupOverlayApi14.createFrom(group);
+    }
+
+    /**
+     * Provides access to the hidden ViewGroup#suppressLayout method.
+     */
+    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            ViewGroupUtilsApi18.suppressLayout(group, suppress);
+        } else {
+            ViewGroupUtilsApi14.suppressLayout(group, suppress);
+        }
+    }
+
+    private ViewGroupUtils() {
+    }
+}
diff --git a/androidx/transition/ViewGroupUtilsApi14.java b/androidx/transition/ViewGroupUtilsApi14.java
new file mode 100644
index 0000000..32d546a
--- /dev/null
+++ b/androidx/transition/ViewGroupUtilsApi14.java
@@ -0,0 +1,131 @@
+/*
+ * 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 androidx.transition;
+
+import android.animation.LayoutTransition;
+import android.util.Log;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class ViewGroupUtilsApi14 {
+
+    private static final String TAG = "ViewGroupUtilsApi14";
+
+    private static final int LAYOUT_TRANSITION_CHANGING = 4;
+
+    private static LayoutTransition sEmptyLayoutTransition;
+
+    private static Field sLayoutSuppressedField;
+    private static boolean sLayoutSuppressedFieldFetched;
+
+    private static Method sCancelMethod;
+    private static boolean sCancelMethodFetched;
+
+    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
+        // Prepare the dummy LayoutTransition
+        if (sEmptyLayoutTransition == null) {
+            sEmptyLayoutTransition = new LayoutTransition() {
+                @Override
+                public boolean isChangingLayout() {
+                    return true;
+                }
+            };
+            sEmptyLayoutTransition.setAnimator(LayoutTransition.APPEARING, null);
+            sEmptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, null);
+            sEmptyLayoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, null);
+            sEmptyLayoutTransition.setAnimator(LayoutTransition.DISAPPEARING, null);
+            sEmptyLayoutTransition.setAnimator(LAYOUT_TRANSITION_CHANGING, null);
+        }
+        if (suppress) {
+            // Save the current LayoutTransition
+            final LayoutTransition layoutTransition = group.getLayoutTransition();
+            if (layoutTransition != null) {
+                if (layoutTransition.isRunning()) {
+                    cancelLayoutTransition(layoutTransition);
+                }
+                if (layoutTransition != sEmptyLayoutTransition) {
+                    group.setTag(R.id.transition_layout_save, layoutTransition);
+                }
+            }
+            // Suppress the layout
+            group.setLayoutTransition(sEmptyLayoutTransition);
+        } else {
+            // Thaw the layout suppression
+            group.setLayoutTransition(null);
+            // Request layout if necessary
+            if (!sLayoutSuppressedFieldFetched) {
+                try {
+                    sLayoutSuppressedField = ViewGroup.class.getDeclaredField("mLayoutSuppressed");
+                    sLayoutSuppressedField.setAccessible(true);
+                } catch (NoSuchFieldException e) {
+                    Log.i(TAG, "Failed to access mLayoutSuppressed field by reflection");
+                }
+                sLayoutSuppressedFieldFetched = true;
+            }
+            boolean layoutSuppressed = false;
+            if (sLayoutSuppressedField != null) {
+                try {
+                    layoutSuppressed = sLayoutSuppressedField.getBoolean(group);
+                    if (layoutSuppressed) {
+                        sLayoutSuppressedField.setBoolean(group, false);
+                    }
+                } catch (IllegalAccessException e) {
+                    Log.i(TAG, "Failed to get mLayoutSuppressed field by reflection");
+                }
+            }
+            if (layoutSuppressed) {
+                group.requestLayout();
+            }
+            // Restore the saved LayoutTransition
+            final LayoutTransition layoutTransition =
+                    (LayoutTransition) group.getTag(R.id.transition_layout_save);
+            if (layoutTransition != null) {
+                group.setTag(R.id.transition_layout_save, null);
+                group.setLayoutTransition(layoutTransition);
+            }
+        }
+    }
+
+    private static void cancelLayoutTransition(LayoutTransition t) {
+        if (!sCancelMethodFetched) {
+            try {
+                sCancelMethod = LayoutTransition.class.getDeclaredMethod("cancel");
+                sCancelMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to access cancel method by reflection");
+            }
+            sCancelMethodFetched = true;
+        }
+        if (sCancelMethod != null) {
+            try {
+                sCancelMethod.invoke(t);
+            } catch (IllegalAccessException e) {
+                Log.i(TAG, "Failed to access cancel method by reflection");
+            } catch (InvocationTargetException e) {
+                Log.i(TAG, "Failed to invoke cancel method by reflection");
+            }
+        }
+    }
+
+    private ViewGroupUtilsApi14() {
+    }
+}
diff --git a/androidx/transition/ViewGroupUtilsApi18.java b/androidx/transition/ViewGroupUtilsApi18.java
new file mode 100644
index 0000000..e4d4ffa
--- /dev/null
+++ b/androidx/transition/ViewGroupUtilsApi18.java
@@ -0,0 +1,64 @@
+/*
+ * 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 androidx.transition;
+
+import android.util.Log;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@RequiresApi(18)
+class ViewGroupUtilsApi18 {
+
+    private static final String TAG = "ViewUtilsApi18";
+
+    private static Method sSuppressLayoutMethod;
+    private static boolean sSuppressLayoutMethodFetched;
+
+    static void suppressLayout(@NonNull ViewGroup group, boolean suppress) {
+        fetchSuppressLayoutMethod();
+        if (sSuppressLayoutMethod != null) {
+            try {
+                sSuppressLayoutMethod.invoke(group, suppress);
+            } catch (IllegalAccessException e) {
+                Log.i(TAG, "Failed to invoke suppressLayout method", e);
+            } catch (InvocationTargetException e) {
+                Log.i(TAG, "Error invoking suppressLayout method", e);
+            }
+        }
+    }
+
+    private static void fetchSuppressLayoutMethod() {
+        if (!sSuppressLayoutMethodFetched) {
+            try {
+                sSuppressLayoutMethod = ViewGroup.class.getDeclaredMethod("suppressLayout",
+                        boolean.class);
+                sSuppressLayoutMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve suppressLayout method", e);
+            }
+            sSuppressLayoutMethodFetched = true;
+        }
+    }
+
+    private ViewGroupUtilsApi18() {
+    }
+}
diff --git a/androidx/transition/ViewOverlayApi14.java b/androidx/transition/ViewOverlayApi14.java
new file mode 100644
index 0000000..049e685
--- /dev/null
+++ b/androidx/transition/ViewOverlayApi14.java
@@ -0,0 +1,356 @@
+/*
+ * 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.core.view.ViewCompat;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+
+class ViewOverlayApi14 implements ViewOverlayImpl {
+
+    /**
+     * The actual container for the drawables (and views, if it's a ViewGroupOverlay).
+     * All of the management and rendering details for the overlay are handled in
+     * OverlayViewGroup.
+     */
+    protected OverlayViewGroup mOverlayViewGroup;
+
+    ViewOverlayApi14(Context context, ViewGroup hostView, View requestingView) {
+        mOverlayViewGroup = new OverlayViewGroup(context, hostView, requestingView, this);
+    }
+
+    static ViewGroup getContentView(View view) {
+        View parent = view;
+        while (parent != null) {
+            if (parent.getId() == android.R.id.content && parent instanceof ViewGroup) {
+                return (ViewGroup) parent;
+            }
+            if (parent.getParent() instanceof ViewGroup) {
+                parent = (ViewGroup) parent.getParent();
+            }
+        }
+        return null;
+    }
+
+    static ViewOverlayApi14 createFrom(View view) {
+        ViewGroup contentView = getContentView(view);
+        if (contentView != null) {
+            final int numChildren = contentView.getChildCount();
+            for (int i = 0; i < numChildren; ++i) {
+                View child = contentView.getChildAt(i);
+                if (child instanceof OverlayViewGroup) {
+                    return ((OverlayViewGroup) child).mViewOverlay;
+                }
+            }
+            return new ViewGroupOverlayApi14(contentView.getContext(), contentView, view);
+        }
+        return null;
+    }
+
+    /**
+     * Used internally by View and ViewGroup to handle drawing and invalidation
+     * of the overlay
+     */
+    ViewGroup getOverlayView() {
+        return mOverlayViewGroup;
+    }
+
+    @Override
+    public void add(@NonNull Drawable drawable) {
+        mOverlayViewGroup.add(drawable);
+    }
+
+    @Override
+    public void clear() {
+        mOverlayViewGroup.clear();
+    }
+
+    @Override
+    public void remove(@NonNull Drawable drawable) {
+        mOverlayViewGroup.remove(drawable);
+    }
+
+    boolean isEmpty() {
+        return mOverlayViewGroup.isEmpty();
+    }
+
+
+    /**
+     * OverlayViewGroup is a container that View and ViewGroup use to host
+     * drawables and views added to their overlays  ({@code ViewOverlay} and
+     * {@code ViewGroupOverlay}, respectively). Drawables are added to the overlay
+     * via the add/remove methods in ViewOverlay, Views are added/removed via
+     * ViewGroupOverlay. These drawable and view objects are
+     * drawn whenever the view itself is drawn; first the view draws its own
+     * content (and children, if it is a ViewGroup), then it draws its overlay
+     * (if it has one).
+     *
+     * <p>Besides managing and drawing the list of drawables, this class serves
+     * two purposes:
+     * (1) it noops layout calls because children are absolutely positioned and
+     * (2) it forwards all invalidation calls to its host view. The invalidation
+     * redirect is necessary because the overlay is not a child of the host view
+     * and invalidation cannot therefore follow the normal path up through the
+     * parent hierarchy.</p>
+     *
+     * @see View#getOverlay()
+     * @see ViewGroup#getOverlay()
+     */
+    static class OverlayViewGroup extends ViewGroup {
+
+        static Method sInvalidateChildInParentFastMethod;
+
+        static {
+            try {
+                sInvalidateChildInParentFastMethod = ViewGroup.class.getDeclaredMethod(
+                        "invalidateChildInParentFast", int.class, int.class, Rect.class);
+            } catch (NoSuchMethodException e) {
+            }
+
+        }
+
+        /**
+         * The View for which this is an overlay. Invalidations of the overlay are redirected to
+         * this host view.
+         */
+        ViewGroup mHostView;
+        View mRequestingView;
+        /**
+         * The set of drawables to draw when the overlay is rendered.
+         */
+        ArrayList<Drawable> mDrawables = null;
+        /**
+         * Reference to the hosting overlay object
+         */
+        ViewOverlayApi14 mViewOverlay;
+
+        OverlayViewGroup(Context context, ViewGroup hostView, View requestingView,
+                ViewOverlayApi14 viewOverlay) {
+            super(context);
+            mHostView = hostView;
+            mRequestingView = requestingView;
+            setRight(hostView.getWidth());
+            setBottom(hostView.getHeight());
+            hostView.addView(this);
+            mViewOverlay = viewOverlay;
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent ev) {
+            // Intercept and noop all touch events - overlays do not allow touch events
+            return false;
+        }
+
+        public void add(Drawable drawable) {
+            if (mDrawables == null) {
+
+                mDrawables = new ArrayList<>();
+            }
+            if (!mDrawables.contains(drawable)) {
+                // Make each drawable unique in the overlay; can't add it more than once
+                mDrawables.add(drawable);
+                invalidate(drawable.getBounds());
+                drawable.setCallback(this);
+            }
+        }
+
+        public void remove(Drawable drawable) {
+            if (mDrawables != null) {
+                mDrawables.remove(drawable);
+                invalidate(drawable.getBounds());
+                drawable.setCallback(null);
+            }
+        }
+
+        @Override
+        protected boolean verifyDrawable(@NonNull Drawable who) {
+            return super.verifyDrawable(who) || (mDrawables != null && mDrawables.contains(who));
+        }
+
+        public void add(View child) {
+            if (child.getParent() instanceof ViewGroup) {
+                ViewGroup parent = (ViewGroup) child.getParent();
+                if (parent != mHostView && parent.getParent() != null
+                        && ViewCompat.isAttachedToWindow(parent)) {
+                    // Moving to different container; figure out how to position child such that
+                    // it is in the same location on the screen
+                    int[] parentLocation = new int[2];
+                    int[] hostViewLocation = new int[2];
+                    parent.getLocationOnScreen(parentLocation);
+                    mHostView.getLocationOnScreen(hostViewLocation);
+                    ViewCompat.offsetLeftAndRight(child, parentLocation[0] - hostViewLocation[0]);
+                    ViewCompat.offsetTopAndBottom(child, parentLocation[1] - hostViewLocation[1]);
+                }
+                parent.removeView(child);
+//                if (parent.getLayoutTransition() != null) {
+//                    // LayoutTransition will cause the child to delay removal - cancel it
+//                    parent.getLayoutTransition().cancel(LayoutTransition.DISAPPEARING);
+//                }
+                // fail-safe if view is still attached for any reason
+                if (child.getParent() != null) {
+                    parent.removeView(child);
+                }
+            }
+            super.addView(child, getChildCount() - 1);
+        }
+
+        public void remove(View view) {
+            super.removeView(view);
+            if (isEmpty()) {
+                mHostView.removeView(this);
+            }
+        }
+
+        public void clear() {
+            removeAllViews();
+            if (mDrawables != null) {
+                mDrawables.clear();
+            }
+        }
+
+        boolean isEmpty() {
+            return getChildCount() == 0
+                    && (mDrawables == null || mDrawables.size() == 0);
+        }
+
+        @Override
+        public void invalidateDrawable(@NonNull Drawable drawable) {
+            invalidate(drawable.getBounds());
+        }
+
+        @Override
+        protected void dispatchDraw(Canvas canvas) {
+            int[] contentViewLocation = new int[2];
+            int[] hostViewLocation = new int[2];
+            mHostView.getLocationOnScreen(contentViewLocation);
+            mRequestingView.getLocationOnScreen(hostViewLocation);
+            canvas.translate(hostViewLocation[0] - contentViewLocation[0],
+                    hostViewLocation[1] - contentViewLocation[1]);
+            canvas.clipRect(
+                    new Rect(0, 0, mRequestingView.getWidth(), mRequestingView.getHeight()));
+            super.dispatchDraw(canvas);
+            final int numDrawables = (mDrawables == null) ? 0 : mDrawables.size();
+            for (int i = 0; i < numDrawables; ++i) {
+                mDrawables.get(i).draw(canvas);
+            }
+        }
+
+        @Override
+        protected void onLayout(boolean changed, int l, int t, int r, int b) {
+            // Noop: children are positioned absolutely
+        }
+
+        /*
+         The following invalidation overrides exist for the purpose of redirecting invalidation to
+         the host view. The overlay is not parented to the host view (since a View cannot be a
+         parent), so the invalidation cannot proceed through the normal parent hierarchy.
+         There is a built-in assumption that the overlay exactly covers the host view, therefore
+         the invalidation rectangles received do not need to be adjusted when forwarded to
+         the host view.
+         */
+
+        private void getOffset(int[] offset) {
+            int[] contentViewLocation = new int[2];
+            int[] hostViewLocation = new int[2];
+            mHostView.getLocationOnScreen(contentViewLocation);
+            mRequestingView.getLocationOnScreen(hostViewLocation);
+            offset[0] = hostViewLocation[0] - contentViewLocation[0];
+            offset[1] = hostViewLocation[1] - contentViewLocation[1];
+        }
+
+        public void invalidateChildFast(View child, final Rect dirty) {
+            if (mHostView != null) {
+                // Note: This is not a "fast" invalidation. Would be nice to instead invalidate
+                // using DisplayList properties and a dirty rect instead of causing a real
+                // invalidation of the host view
+                int left = child.getLeft();
+                int top = child.getTop();
+                int[] offset = new int[2];
+                getOffset(offset);
+                // TODO: implement transforms
+//                if (!child.getMatrix().isIdentity()) {
+//                    child.transformRect(dirty);
+//                }
+                dirty.offset(left + offset[0], top + offset[1]);
+                mHostView.invalidate(dirty);
+            }
+        }
+
+        /**
+         * @hide
+         */
+        @RestrictTo(LIBRARY_GROUP)
+        protected ViewParent invalidateChildInParentFast(int left, int top, Rect dirty) {
+            if (mHostView instanceof ViewGroup && sInvalidateChildInParentFastMethod != null) {
+                try {
+                    int[] offset = new int[2];
+                    getOffset(offset);
+                    sInvalidateChildInParentFastMethod.invoke(mHostView, left, top, dirty);
+                } catch (IllegalAccessException e) {
+                    e.printStackTrace();
+                } catch (InvocationTargetException e) {
+                    e.printStackTrace();
+                }
+            }
+            return null;
+        }
+
+        @SuppressWarnings("deprecation")
+        @Override
+        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
+            if (mHostView != null) {
+                dirty.offset(location[0], location[1]);
+                if (mHostView instanceof ViewGroup) {
+                    location[0] = 0;
+                    location[1] = 0;
+                    int[] offset = new int[2];
+                    getOffset(offset);
+                    dirty.offset(offset[0], offset[1]);
+                    return super.invalidateChildInParent(location, dirty);
+//                    return ((ViewGroup) mHostView).invalidateChildInParent(location, dirty);
+                } else {
+                    invalidate(dirty);
+                }
+            }
+            return null;
+        }
+
+        static class TouchInterceptor extends View {
+            TouchInterceptor(Context context) {
+                super(context);
+            }
+        }
+    }
+
+    private ViewOverlayApi14() {
+    }
+}
diff --git a/androidx/transition/ViewOverlayApi18.java b/androidx/transition/ViewOverlayApi18.java
new file mode 100644
index 0000000..056123d
--- /dev/null
+++ b/androidx/transition/ViewOverlayApi18.java
@@ -0,0 +1,50 @@
+/*
+ * 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 androidx.transition;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.view.ViewOverlay;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(18)
+class ViewOverlayApi18 implements ViewOverlayImpl {
+
+    private final ViewOverlay mViewOverlay;
+
+    ViewOverlayApi18(@NonNull View view) {
+        mViewOverlay = view.getOverlay();
+    }
+
+    @Override
+    public void add(@NonNull Drawable drawable) {
+        mViewOverlay.add(drawable);
+    }
+
+    @Override
+    public void clear() {
+        mViewOverlay.clear();
+    }
+
+    @Override
+    public void remove(@NonNull Drawable drawable) {
+        mViewOverlay.remove(drawable);
+    }
+
+}
diff --git a/androidx/transition/ViewOverlayImpl.java b/androidx/transition/ViewOverlayImpl.java
new file mode 100644
index 0000000..355bd06
--- /dev/null
+++ b/androidx/transition/ViewOverlayImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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 androidx.transition;
+
+import android.graphics.drawable.Drawable;
+
+import androidx.annotation.NonNull;
+
+interface ViewOverlayImpl {
+
+    /**
+     * Adds a Drawable to the overlay. The bounds of the drawable should be relative to
+     * the host view. Any drawable added to the overlay should be removed when it is no longer
+     * needed or no longer visible.
+     *
+     * @param drawable The Drawable to be added to the overlay. This drawable will be
+     *                 drawn when the view redraws its overlay.
+     * @see #remove(Drawable)
+     */
+    void add(@NonNull Drawable drawable);
+
+    /**
+     * Removes all content from the overlay.
+     */
+    void clear();
+
+    /**
+     * Removes the specified Drawable from the overlay.
+     *
+     * @param drawable The Drawable to be removed from the overlay.
+     * @see #add(Drawable)
+     */
+    void remove(@NonNull Drawable drawable);
+
+}
diff --git a/androidx/transition/ViewUtils.java b/androidx/transition/ViewUtils.java
new file mode 100644
index 0000000..d770ab6
--- /dev/null
+++ b/androidx/transition/ViewUtils.java
@@ -0,0 +1,227 @@
+/*
+ * 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.Build;
+import android.util.Log;
+import android.util.Property;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.view.ViewCompat;
+
+import java.lang.reflect.Field;
+
+/**
+ * Compatibility utilities for platform features of {@link View}.
+ */
+class ViewUtils {
+
+    private static final ViewUtilsBase IMPL;
+    private static final String TAG = "ViewUtils";
+
+    private static Field sViewFlagsField;
+    private static boolean sViewFlagsFieldFetched;
+    private static final int VISIBILITY_MASK = 0x0000000C;
+
+    static {
+        if (Build.VERSION.SDK_INT >= 22) {
+            IMPL = new ViewUtilsApi22();
+        } else if (Build.VERSION.SDK_INT >= 21) {
+            IMPL = new ViewUtilsApi21();
+        } else if (Build.VERSION.SDK_INT >= 19) {
+            IMPL = new ViewUtilsApi19();
+        } else {
+            IMPL = new ViewUtilsBase();
+        }
+    }
+
+    /**
+     * A {@link Property} for animating transitionAlpha value of a View.
+     */
+    static final Property<View, Float> TRANSITION_ALPHA =
+            new Property<View, Float>(Float.class, "translationAlpha") {
+
+                @Override
+                public Float get(View view) {
+                    return getTransitionAlpha(view);
+                }
+
+                @Override
+                public void set(View view, Float alpha) {
+                    setTransitionAlpha(view, alpha);
+                }
+
+            };
+
+    static final Property<View, Rect> CLIP_BOUNDS =
+            new Property<View, Rect>(Rect.class, "clipBounds") {
+
+                @Override
+                public Rect get(View view) {
+                    return ViewCompat.getClipBounds(view);
+                }
+
+                @Override
+                public void set(View view, Rect clipBounds) {
+                    ViewCompat.setClipBounds(view, clipBounds);
+                }
+
+            };
+
+    /**
+     * Backward-compatible {@link View#getOverlay()}.
+     */
+    static ViewOverlayImpl getOverlay(@NonNull View view) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new ViewOverlayApi18(view);
+        }
+        return ViewOverlayApi14.createFrom(view);
+    }
+
+    /**
+     * Backward-compatible {@link View#getWindowId()}.
+     */
+    static WindowIdImpl getWindowId(@NonNull View view) {
+        if (Build.VERSION.SDK_INT >= 18) {
+            return new WindowIdApi18(view);
+        }
+        return new WindowIdApi14(view.getWindowToken());
+    }
+
+    static void setTransitionAlpha(@NonNull View view, float alpha) {
+        IMPL.setTransitionAlpha(view, alpha);
+    }
+
+    static float getTransitionAlpha(@NonNull View view) {
+        return IMPL.getTransitionAlpha(view);
+    }
+
+    /**
+     * This method needs to be called before an animation using {@link #setTransitionAlpha(View,
+     * float)} in order to make its behavior backward-compatible.
+     */
+    static void saveNonTransitionAlpha(@NonNull View view) {
+        IMPL.saveNonTransitionAlpha(view);
+    }
+
+    /**
+     * This method needs to be called after an animation using
+     * {@link #setTransitionAlpha(View, float)} if {@link #saveNonTransitionAlpha(View)} has been
+     * called.
+     */
+    static void clearNonTransitionAlpha(@NonNull View view) {
+        IMPL.clearNonTransitionAlpha(view);
+    }
+
+    /**
+     * Copy of a hidden platform method, View#setTransitionVisibility.
+     *
+     * <p>Change the visibility of the View without triggering any other changes. This is
+     * important for transitions, where visibility changes should not adjust focus or
+     * trigger a new layout. This is only used when the visibility has already been changed
+     * and we need a transient value during an animation. When the animation completes,
+     * the original visibility value is always restored.</p>
+     *
+     * @param view       The target view.
+     * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE}, or
+     *                   {@link View#GONE}.
+     */
+    static void setTransitionVisibility(@NonNull View view, int visibility) {
+        fetchViewFlagsField();
+        if (sViewFlagsField != null) {
+            try {
+                int viewFlags = sViewFlagsField.getInt(view);
+                sViewFlagsField.setInt(view, (viewFlags & ~VISIBILITY_MASK) | visibility);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            }
+        }
+    }
+
+    /**
+     * Modifies the input matrix such that it maps view-local coordinates to
+     * on-screen coordinates.
+     *
+     * <p>On API Level 21 and above, this includes transformation matrix applied to {@code
+     * ViewRootImpl}, but not on older platforms. This difference is balanced out by the
+     * implementation difference in other related platform APIs and their backport, such as
+     * GhostView.</p>
+     *
+     * @param view   target view
+     * @param matrix input matrix to modify
+     */
+    static void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
+        IMPL.transformMatrixToGlobal(view, matrix);
+    }
+
+    /**
+     * Modifies the input matrix such that it maps on-screen coordinates to
+     * view-local coordinates.
+     *
+     * <p>On API Level 21 and above, this includes transformation matrix applied to {@code
+     * ViewRootImpl}, but not on older platforms. This difference is balanced out by the
+     * implementation difference in other related platform APIs and their backport, such as
+     * GhostView.</p>
+     *
+     * @param view   target view
+     * @param matrix input matrix to modify
+     */
+    static void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
+        IMPL.transformMatrixToLocal(view, matrix);
+    }
+
+    /**
+     * Sets the transformation matrix for animation.
+     *
+     * @param v The view
+     * @param m The matrix
+     */
+    static void setAnimationMatrix(@NonNull View v, @Nullable Matrix m) {
+        IMPL.setAnimationMatrix(v, m);
+    }
+
+    /**
+     * Assign a size and position to this view.
+     *
+     * @param left   Left position, relative to parent
+     * @param top    Top position, relative to parent
+     * @param right  Right position, relative to parent
+     * @param bottom Bottom position, relative to parent
+     */
+    static void setLeftTopRightBottom(@NonNull View v, int left, int top, int right, int bottom) {
+        IMPL.setLeftTopRightBottom(v, left, top, right, bottom);
+    }
+
+    private static void fetchViewFlagsField() {
+        if (!sViewFlagsFieldFetched) {
+            try {
+                sViewFlagsField = View.class.getDeclaredField("mViewFlags");
+                sViewFlagsField.setAccessible(true);
+            } catch (NoSuchFieldException e) {
+                Log.i(TAG, "fetchViewFlagsField: ");
+            }
+            sViewFlagsFieldFetched = true;
+        }
+    }
+
+    private ViewUtils() {
+    }
+}
diff --git a/androidx/transition/ViewUtilsApi19.java b/androidx/transition/ViewUtilsApi19.java
new file mode 100644
index 0000000..ff60431
--- /dev/null
+++ b/androidx/transition/ViewUtilsApi19.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@RequiresApi(19)
+class ViewUtilsApi19 extends ViewUtilsBase {
+
+    private static final String TAG = "ViewUtilsApi19";
+
+    private static Method sSetTransitionAlphaMethod;
+    private static boolean sSetTransitionAlphaMethodFetched;
+    private static Method sGetTransitionAlphaMethod;
+    private static boolean sGetTransitionAlphaMethodFetched;
+
+    @Override
+    public void setTransitionAlpha(@NonNull View view, float alpha) {
+        fetchSetTransitionAlphaMethod();
+        if (sSetTransitionAlphaMethod != null) {
+            try {
+                sSetTransitionAlphaMethod.invoke(view, alpha);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        } else {
+            view.setAlpha(alpha);
+        }
+    }
+
+    @Override
+    public float getTransitionAlpha(@NonNull View view) {
+        fetchGetTransitionAlphaMethod();
+        if (sGetTransitionAlphaMethod != null) {
+            try {
+                return (Float) sGetTransitionAlphaMethod.invoke(view);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+        return super.getTransitionAlpha(view);
+    }
+
+    @Override
+    public void saveNonTransitionAlpha(@NonNull View view) {
+        // Do nothing
+    }
+
+    @Override
+    public void clearNonTransitionAlpha(@NonNull View view) {
+        // Do nothing
+    }
+
+    private void fetchSetTransitionAlphaMethod() {
+        if (!sSetTransitionAlphaMethodFetched) {
+            try {
+                sSetTransitionAlphaMethod = View.class.getDeclaredMethod("setTransitionAlpha",
+                        float.class);
+                sSetTransitionAlphaMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve setTransitionAlpha method", e);
+            }
+            sSetTransitionAlphaMethodFetched = true;
+        }
+    }
+
+    private void fetchGetTransitionAlphaMethod() {
+        if (!sGetTransitionAlphaMethodFetched) {
+            try {
+                sGetTransitionAlphaMethod = View.class.getDeclaredMethod("getTransitionAlpha");
+                sGetTransitionAlphaMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve getTransitionAlpha method", e);
+            }
+            sGetTransitionAlphaMethodFetched = true;
+        }
+    }
+
+}
diff --git a/androidx/transition/ViewUtilsApi21.java b/androidx/transition/ViewUtilsApi21.java
new file mode 100644
index 0000000..14301d2
--- /dev/null
+++ b/androidx/transition/ViewUtilsApi21.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@RequiresApi(21)
+class ViewUtilsApi21 extends ViewUtilsApi19 {
+
+    private static final String TAG = "ViewUtilsApi21";
+
+    private static Method sTransformMatrixToGlobalMethod;
+    private static boolean sTransformMatrixToGlobalMethodFetched;
+    private static Method sTransformMatrixToLocalMethod;
+    private static boolean sTransformMatrixToLocalMethodFetched;
+    private static Method sSetAnimationMatrixMethod;
+    private static boolean sSetAnimationMatrixMethodFetched;
+
+    @Override
+    public void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
+        fetchTransformMatrixToGlobalMethod();
+        if (sTransformMatrixToGlobalMethod != null) {
+            try {
+                sTransformMatrixToGlobalMethod.invoke(view, matrix);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+    }
+
+    @Override
+    public void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
+        fetchTransformMatrixToLocalMethod();
+        if (sTransformMatrixToLocalMethod != null) {
+            try {
+                sTransformMatrixToLocalMethod.invoke(view, matrix);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+    }
+
+    @Override
+    public void setAnimationMatrix(@NonNull View view, Matrix matrix) {
+        fetchSetAnimationMatrix();
+        if (sSetAnimationMatrixMethod != null) {
+            try {
+                sSetAnimationMatrixMethod.invoke(view, matrix);
+            } catch (InvocationTargetException e) {
+                // Do nothing
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+    }
+
+    private void fetchTransformMatrixToGlobalMethod() {
+        if (!sTransformMatrixToGlobalMethodFetched) {
+            try {
+                sTransformMatrixToGlobalMethod = View.class.getDeclaredMethod(
+                        "transformMatrixToGlobal", Matrix.class);
+                sTransformMatrixToGlobalMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve transformMatrixToGlobal method", e);
+            }
+            sTransformMatrixToGlobalMethodFetched = true;
+        }
+    }
+
+    private void fetchTransformMatrixToLocalMethod() {
+        if (!sTransformMatrixToLocalMethodFetched) {
+            try {
+                sTransformMatrixToLocalMethod = View.class.getDeclaredMethod(
+                        "transformMatrixToLocal", Matrix.class);
+                sTransformMatrixToLocalMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve transformMatrixToLocal method", e);
+            }
+            sTransformMatrixToLocalMethodFetched = true;
+        }
+    }
+
+    private void fetchSetAnimationMatrix() {
+        if (!sSetAnimationMatrixMethodFetched) {
+            try {
+                sSetAnimationMatrixMethod = View.class.getDeclaredMethod(
+                        "setAnimationMatrix", Matrix.class);
+                sSetAnimationMatrixMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve setAnimationMatrix method", e);
+            }
+            sSetAnimationMatrixMethodFetched = true;
+        }
+    }
+
+}
diff --git a/androidx/transition/ViewUtilsApi22.java b/androidx/transition/ViewUtilsApi22.java
new file mode 100644
index 0000000..f8dd2a0
--- /dev/null
+++ b/androidx/transition/ViewUtilsApi22.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.annotation.SuppressLint;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.RequiresApi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@RequiresApi(22)
+class ViewUtilsApi22 extends ViewUtilsApi21 {
+
+    private static final String TAG = "ViewUtilsApi22";
+
+    private static Method sSetLeftTopRightBottomMethod;
+    private static boolean sSetLeftTopRightBottomMethodFetched;
+
+    @Override
+    public void setLeftTopRightBottom(View v, int left, int top, int right, int bottom) {
+        fetchSetLeftTopRightBottomMethod();
+        if (sSetLeftTopRightBottomMethod != null) {
+            try {
+                sSetLeftTopRightBottomMethod.invoke(v, left, top, right, bottom);
+            } catch (IllegalAccessException e) {
+                // Do nothing
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException(e.getCause());
+            }
+        }
+    }
+
+    @SuppressLint("PrivateApi")
+    private void fetchSetLeftTopRightBottomMethod() {
+        if (!sSetLeftTopRightBottomMethodFetched) {
+            try {
+                sSetLeftTopRightBottomMethod = View.class.getDeclaredMethod("setLeftTopRightBottom",
+                        int.class, int.class, int.class, int.class);
+                sSetLeftTopRightBottomMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.i(TAG, "Failed to retrieve setLeftTopRightBottom method", e);
+            }
+            sSetLeftTopRightBottomMethodFetched = true;
+        }
+    }
+
+}
+
diff --git a/androidx/transition/ViewUtilsBase.java b/androidx/transition/ViewUtilsBase.java
new file mode 100644
index 0000000..c3dad8f
--- /dev/null
+++ b/androidx/transition/ViewUtilsBase.java
@@ -0,0 +1,132 @@
+/*
+ * 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 androidx.transition;
+
+import android.graphics.Matrix;
+import android.view.View;
+import android.view.ViewParent;
+
+import androidx.annotation.NonNull;
+
+class ViewUtilsBase {
+
+    private float[] mMatrixValues;
+
+    public void setTransitionAlpha(@NonNull View view, float alpha) {
+        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
+        if (savedAlpha != null) {
+            view.setAlpha(savedAlpha * alpha);
+        } else {
+            view.setAlpha(alpha);
+        }
+    }
+
+    public float getTransitionAlpha(@NonNull View view) {
+        Float savedAlpha = (Float) view.getTag(R.id.save_non_transition_alpha);
+        if (savedAlpha != null) {
+            return view.getAlpha() / savedAlpha;
+        } else {
+            return view.getAlpha();
+        }
+    }
+
+    public void saveNonTransitionAlpha(@NonNull View view) {
+        if (view.getTag(R.id.save_non_transition_alpha) == null) {
+            view.setTag(R.id.save_non_transition_alpha, view.getAlpha());
+        }
+    }
+
+    public void clearNonTransitionAlpha(@NonNull View view) {
+        // We don't clear the saved value when the view is hidden; that's the situation we are
+        // saving this value for.
+        if (view.getVisibility() == View.VISIBLE) {
+            view.setTag(R.id.save_non_transition_alpha, null);
+        }
+    }
+
+    public void transformMatrixToGlobal(@NonNull View view, @NonNull Matrix matrix) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            transformMatrixToGlobal(vp, matrix);
+            matrix.preTranslate(-vp.getScrollX(), -vp.getScrollY());
+        }
+        matrix.preTranslate(view.getLeft(), view.getTop());
+        final Matrix vm = view.getMatrix();
+        if (!vm.isIdentity()) {
+            matrix.preConcat(vm);
+        }
+    }
+
+    public void transformMatrixToLocal(@NonNull View view, @NonNull Matrix matrix) {
+        final ViewParent parent = view.getParent();
+        if (parent instanceof View) {
+            final View vp = (View) parent;
+            transformMatrixToLocal(vp, matrix);
+            matrix.postTranslate(vp.getScrollX(), vp.getScrollY());
+        }
+        matrix.postTranslate(view.getLeft(), view.getTop());
+        final Matrix vm = view.getMatrix();
+        if (!vm.isIdentity()) {
+            final Matrix inverted = new Matrix();
+            if (vm.invert(inverted)) {
+                matrix.postConcat(inverted);
+            }
+        }
+    }
+
+    public void setAnimationMatrix(@NonNull View view, Matrix matrix) {
+        if (matrix == null || matrix.isIdentity()) {
+            view.setPivotX(view.getWidth() / 2);
+            view.setPivotY(view.getHeight() / 2);
+            view.setTranslationX(0);
+            view.setTranslationY(0);
+            view.setScaleX(1);
+            view.setScaleY(1);
+            view.setRotation(0);
+        } else {
+            float[] values = mMatrixValues;
+            if (values == null) {
+                mMatrixValues = values = new float[9];
+            }
+            matrix.getValues(values);
+            final float sin = values[Matrix.MSKEW_Y];
+            final float cos = (float) Math.sqrt(1 - sin * sin)
+                    * (values[Matrix.MSCALE_X] < 0 ? -1 : 1);
+            final float rotation = (float) Math.toDegrees(Math.atan2(sin, cos));
+            final float scaleX = values[Matrix.MSCALE_X] / cos;
+            final float scaleY = values[Matrix.MSCALE_Y] / cos;
+            final float dx = values[Matrix.MTRANS_X];
+            final float dy = values[Matrix.MTRANS_Y];
+            view.setPivotX(0);
+            view.setPivotY(0);
+            view.setTranslationX(dx);
+            view.setTranslationY(dy);
+            view.setRotation(rotation);
+            view.setScaleX(scaleX);
+            view.setScaleY(scaleY);
+        }
+    }
+
+    public void setLeftTopRightBottom(View v, int left, int top, int right, int bottom) {
+        v.setLeft(left);
+        v.setTop(top);
+        v.setRight(right);
+        v.setBottom(bottom);
+    }
+
+}
diff --git a/androidx/transition/Visibility.java b/androidx/transition/Visibility.java
new file mode 100644
index 0000000..f26a547
--- /dev/null
+++ b/androidx/transition/Visibility.java
@@ -0,0 +1,574 @@
+/*
+ * 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 androidx.transition;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.core.content.res.TypedArrayUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes. Visibility is determined not just by the
+ * {@link View#setVisibility(int)} state of views, but also whether
+ * views exist in the current view hierarchy. The class is intended to be a
+ * utility for subclasses such as {@link Fade}, which use this visibility
+ * information to determine the specific animations to run when visibility
+ * changes occur. Subclasses should implement one or both of the methods
+ * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
+ */
+public abstract class Visibility extends Transition {
+
+    static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
+    private static final String PROPNAME_PARENT = "android:visibility:parent";
+    private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
+
+    /**
+     * Mode used in {@link #setMode(int)} to make the transition
+     * operate on targets that are appearing. Maybe be combined with
+     * {@link #MODE_OUT} to target Visibility changes both in and out.
+     */
+    public static final int MODE_IN = 0x1;
+
+    /**
+     * Mode used in {@link #setMode(int)} to make the transition
+     * operate on targets that are disappearing. Maybe be combined with
+     * {@link #MODE_IN} to target Visibility changes both in and out.
+     */
+    public static final int MODE_OUT = 0x2;
+
+    /** @hide */
+    @RestrictTo(LIBRARY_GROUP)
+    @IntDef(flag = true, value = {MODE_IN, MODE_OUT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Mode {
+    }
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_VISIBILITY,
+            PROPNAME_PARENT,
+    };
+
+    private static class VisibilityInfo {
+        boolean mVisibilityChange;
+        boolean mFadeIn;
+        int mStartVisibility;
+        int mEndVisibility;
+        ViewGroup mStartParent;
+        ViewGroup mEndParent;
+    }
+
+    private int mMode = MODE_IN | MODE_OUT;
+
+    public Visibility() {
+    }
+
+    public Visibility(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        TypedArray a = context.obtainStyledAttributes(attrs, Styleable.VISIBILITY_TRANSITION);
+        @Mode
+        int mode = TypedArrayUtils.getNamedInt(a, (XmlResourceParser) attrs,
+                "transitionVisibilityMode",
+                Styleable.VisibilityTransition.TRANSITION_VISIBILITY_MODE, 0);
+        a.recycle();
+        if (mode != 0) {
+            setMode(mode);
+        }
+    }
+
+    /**
+     * Changes the transition to support appearing and/or disappearing Views, depending
+     * on <code>mode</code>.
+     *
+     * @param mode The behavior supported by this transition, a combination of
+     *             {@link #MODE_IN} and {@link #MODE_OUT}.
+     */
+    public void setMode(@Mode int mode) {
+        if ((mode & ~(MODE_IN | MODE_OUT)) != 0) {
+            throw new IllegalArgumentException("Only MODE_IN and MODE_OUT flags are allowed");
+        }
+        mMode = mode;
+    }
+
+    /**
+     * Returns whether appearing and/or disappearing Views are supported.
+     *
+     * @return whether appearing and/or disappearing Views are supported. A combination of
+     * {@link #MODE_IN} and {@link #MODE_OUT}.
+     */
+    @Mode
+    public int getMode() {
+        return mMode;
+    }
+
+    @Nullable
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        int visibility = transitionValues.view.getVisibility();
+        transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
+        transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
+        int[] loc = new int[2];
+        transitionValues.view.getLocationOnScreen(loc);
+        transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
+    }
+
+    @Override
+    public void captureStartValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(@NonNull TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    /**
+     * Returns whether the view is 'visible' according to the given values
+     * object. This is determined by testing the same properties in the values
+     * object that are used to determine whether the object is appearing or
+     * disappearing in the {@link
+     * Transition#createAnimator(ViewGroup, TransitionValues, TransitionValues)}
+     * method. This method can be called by, for example, subclasses that want
+     * to know whether the object is visible in the same way that Visibility
+     * determines it for the actual animation.
+     *
+     * @param values The TransitionValues object that holds the information by
+     *               which visibility is determined.
+     * @return True if the view reference by <code>values</code> is visible,
+     * false otherwise.
+     */
+    public boolean isVisible(TransitionValues values) {
+        if (values == null) {
+            return false;
+        }
+        int visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+        View parent = (View) values.values.get(PROPNAME_PARENT);
+
+        return visibility == View.VISIBLE && parent != null;
+    }
+
+    private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
+            TransitionValues endValues) {
+        final VisibilityInfo visInfo = new VisibilityInfo();
+        visInfo.mVisibilityChange = false;
+        visInfo.mFadeIn = false;
+        if (startValues != null && startValues.values.containsKey(PROPNAME_VISIBILITY)) {
+            visInfo.mStartVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
+            visInfo.mStartParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
+        } else {
+            visInfo.mStartVisibility = -1;
+            visInfo.mStartParent = null;
+        }
+        if (endValues != null && endValues.values.containsKey(PROPNAME_VISIBILITY)) {
+            visInfo.mEndVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
+            visInfo.mEndParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
+        } else {
+            visInfo.mEndVisibility = -1;
+            visInfo.mEndParent = null;
+        }
+        if (startValues != null && endValues != null) {
+            if (visInfo.mStartVisibility == visInfo.mEndVisibility
+                    && visInfo.mStartParent == visInfo.mEndParent) {
+                return visInfo;
+            } else {
+                if (visInfo.mStartVisibility != visInfo.mEndVisibility) {
+                    if (visInfo.mStartVisibility == View.VISIBLE) {
+                        visInfo.mFadeIn = false;
+                        visInfo.mVisibilityChange = true;
+                    } else if (visInfo.mEndVisibility == View.VISIBLE) {
+                        visInfo.mFadeIn = true;
+                        visInfo.mVisibilityChange = true;
+                    }
+                    // no visibilityChange if going between INVISIBLE and GONE
+                } else /* if (visInfo.mStartParent != visInfo.mEndParent) */ {
+                    if (visInfo.mEndParent == null) {
+                        visInfo.mFadeIn = false;
+                        visInfo.mVisibilityChange = true;
+                    } else if (visInfo.mStartParent == null) {
+                        visInfo.mFadeIn = true;
+                        visInfo.mVisibilityChange = true;
+                    }
+                }
+            }
+        } else if (startValues == null && visInfo.mEndVisibility == View.VISIBLE) {
+            visInfo.mFadeIn = true;
+            visInfo.mVisibilityChange = true;
+        } else if (endValues == null && visInfo.mStartVisibility == View.VISIBLE) {
+            visInfo.mFadeIn = false;
+            visInfo.mVisibilityChange = true;
+        }
+        return visInfo;
+    }
+
+    @Nullable
+    @Override
+    public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+            @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
+        VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
+        if (visInfo.mVisibilityChange
+                && (visInfo.mStartParent != null || visInfo.mEndParent != null)) {
+            if (visInfo.mFadeIn) {
+                return onAppear(sceneRoot, startValues, visInfo.mStartVisibility,
+                        endValues, visInfo.mEndVisibility);
+            } else {
+                return onDisappear(sceneRoot, startValues, visInfo.mStartVisibility,
+                        endValues, visInfo.mEndVisibility
+                );
+            }
+        }
+        return null;
+    }
+
+    /**
+     * The default implementation of this method does nothing. Subclasses
+     * should override if they need to create an Animator when targets appear.
+     * The method should only be called by the Visibility class; it is
+     * not intended to be called from external classes.
+     *
+     * @param sceneRoot       The root of the transition hierarchy
+     * @param startValues     The target values in the start scene
+     * @param startVisibility The target visibility in the start scene
+     * @param endValues       The target values in the end scene
+     * @param endVisibility   The target visibility in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public Animator onAppear(ViewGroup sceneRoot, TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        if ((mMode & MODE_IN) != MODE_IN || endValues == null) {
+            return null;
+        }
+        if (startValues == null) {
+            View endParent = (View) endValues.view.getParent();
+            TransitionValues startParentValues = getMatchedTransitionValues(endParent,
+                    false);
+            TransitionValues endParentValues = getTransitionValues(endParent, false);
+            VisibilityInfo parentVisibilityInfo =
+                    getVisibilityChangeInfo(startParentValues, endParentValues);
+            if (parentVisibilityInfo.mVisibilityChange) {
+                return null;
+            }
+        }
+        return onAppear(sceneRoot, endValues.view, startValues, endValues);
+    }
+
+    /**
+     * The default implementation of this method returns a null Animator. Subclasses should
+     * override this method to make targets appear with the desired transition. The
+     * method should only be called from
+     * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+     *
+     * @param sceneRoot   The root of the transition hierarchy
+     * @param view        The View to make appear. This will be in the target scene's View
+     *                    hierarchy
+     *                    and
+     *                    will be VISIBLE.
+     * @param startValues The target values in the start scene
+     * @param endValues   The target values in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
+        return null;
+    }
+
+    /**
+     * The default implementation of this method does nothing. Subclasses
+     * should override if they need to create an Animator when targets disappear.
+     * The method should only be called by the Visibility class; it is
+     * not intended to be called from external classes.
+     *
+     * @param sceneRoot       The root of the transition hierarchy
+     * @param startValues     The target values in the start scene
+     * @param startVisibility The target visibility in the start scene
+     * @param endValues       The target values in the end scene
+     * @param endVisibility   The target visibility in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    @SuppressWarnings("UnusedParameters")
+    public Animator onDisappear(ViewGroup sceneRoot, TransitionValues startValues,
+            int startVisibility, TransitionValues endValues, int endVisibility) {
+        if ((mMode & MODE_OUT) != MODE_OUT) {
+            return null;
+        }
+
+        View startView = (startValues != null) ? startValues.view : null;
+        View endView = (endValues != null) ? endValues.view : null;
+        View overlayView = null;
+        View viewToKeep = null;
+        if (endView == null || endView.getParent() == null) {
+            if (endView != null) {
+                // endView was removed from its parent - add it to the overlay
+                overlayView = endView;
+            } else if (startView != null) {
+                // endView does not exist. Use startView only under certain
+                // conditions, because placing a view in an overlay necessitates
+                // it being removed from its current parent
+                if (startView.getParent() == null) {
+                    // no parent - safe to use
+                    overlayView = startView;
+                } else if (startView.getParent() instanceof View) {
+                    View startParent = (View) startView.getParent();
+                    TransitionValues startParentValues = getTransitionValues(startParent, true);
+                    TransitionValues endParentValues = getMatchedTransitionValues(startParent,
+                            true);
+                    VisibilityInfo parentVisibilityInfo =
+                            getVisibilityChangeInfo(startParentValues, endParentValues);
+                    if (!parentVisibilityInfo.mVisibilityChange) {
+                        overlayView = TransitionUtils.copyViewImage(sceneRoot, startView,
+                                startParent);
+                    } else if (startParent.getParent() == null) {
+                        int id = startParent.getId();
+                        if (id != View.NO_ID && sceneRoot.findViewById(id) != null
+                                && mCanRemoveViews) {
+                            // no parent, but its parent is unparented  but the parent
+                            // hierarchy has been replaced by a new hierarchy with the same id
+                            // and it is safe to un-parent startView
+                            overlayView = startView;
+                        }
+                    }
+                }
+            }
+        } else {
+            // visibility change
+            if (endVisibility == View.INVISIBLE) {
+                viewToKeep = endView;
+            } else {
+                // Becoming GONE
+                if (startView == endView) {
+                    viewToKeep = endView;
+                } else {
+                    overlayView = startView;
+                }
+            }
+        }
+        final int finalVisibility = endVisibility;
+
+        if (overlayView != null && startValues != null) {
+            // TODO: Need to do this for general case of adding to overlay
+            int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
+            int screenX = screenLoc[0];
+            int screenY = screenLoc[1];
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+            overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
+            final ViewGroupOverlayImpl overlay = ViewGroupUtils.getOverlay(sceneRoot);
+            overlay.add(overlayView);
+            Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);
+            if (animator == null) {
+                overlay.remove(overlayView);
+            } else {
+                final View finalOverlayView = overlayView;
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        overlay.remove(finalOverlayView);
+                    }
+                });
+            }
+            return animator;
+        }
+
+        if (viewToKeep != null) {
+            int originalVisibility = viewToKeep.getVisibility();
+            ViewUtils.setTransitionVisibility(viewToKeep, View.VISIBLE);
+            Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
+            if (animator != null) {
+                DisappearListener disappearListener = new DisappearListener(viewToKeep,
+                        finalVisibility, true);
+                animator.addListener(disappearListener);
+                AnimatorUtils.addPauseListener(animator, disappearListener);
+                addListener(disappearListener);
+            } else {
+                ViewUtils.setTransitionVisibility(viewToKeep, originalVisibility);
+            }
+            return animator;
+        }
+        return null;
+    }
+
+    /**
+     * The default implementation of this method returns a null Animator. Subclasses should
+     * override this method to make targets disappear with the desired transition. The
+     * method should only be called from
+     * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+     *
+     * @param sceneRoot   The root of the transition hierarchy
+     * @param view        The View to make disappear. This will be in the target scene's View
+     *                    hierarchy or in an {@link android.view.ViewGroupOverlay} and will be
+     *                    VISIBLE.
+     * @param startValues The target values in the start scene
+     * @param endValues   The target values in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
+        return null;
+    }
+
+    @Override
+    public boolean isTransitionRequired(TransitionValues startValues, TransitionValues newValues) {
+        if (startValues == null && newValues == null) {
+            return false;
+        }
+        if (startValues != null && newValues != null
+                && newValues.values.containsKey(PROPNAME_VISIBILITY)
+                != startValues.values.containsKey(PROPNAME_VISIBILITY)) {
+            // The transition wasn't targeted in either the start or end, so it couldn't
+            // have changed.
+            return false;
+        }
+        VisibilityInfo changeInfo = getVisibilityChangeInfo(startValues, newValues);
+        return changeInfo.mVisibilityChange && (changeInfo.mStartVisibility == View.VISIBLE
+                || changeInfo.mEndVisibility == View.VISIBLE);
+    }
+
+    private static class DisappearListener extends AnimatorListenerAdapter
+            implements TransitionListener, AnimatorUtils.AnimatorPauseListenerCompat {
+
+        private final View mView;
+        private final int mFinalVisibility;
+        private final ViewGroup mParent;
+        private final boolean mSuppressLayout;
+
+        private boolean mLayoutSuppressed;
+        boolean mCanceled = false;
+
+        DisappearListener(View view, int finalVisibility, boolean suppressLayout) {
+            mView = view;
+            mFinalVisibility = finalVisibility;
+            mParent = (ViewGroup) view.getParent();
+            mSuppressLayout = suppressLayout;
+            // Prevent a layout from including mView in its calculation.
+            suppressLayout(true);
+        }
+
+        // This overrides both AnimatorListenerAdapter and
+        // AnimatorUtilsApi14.AnimatorPauseListenerCompat
+        @Override
+        public void onAnimationPause(Animator animation) {
+            if (!mCanceled) {
+                ViewUtils.setTransitionVisibility(mView, mFinalVisibility);
+            }
+        }
+
+        // This overrides both AnimatorListenerAdapter and
+        // AnimatorUtilsApi14.AnimatorPauseListenerCompat
+        @Override
+        public void onAnimationResume(Animator animation) {
+            if (!mCanceled) {
+                ViewUtils.setTransitionVisibility(mView, View.VISIBLE);
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            hideViewWhenNotCanceled();
+        }
+
+        @Override
+        public void onTransitionStart(@NonNull Transition transition) {
+            // Do nothing
+        }
+
+        @Override
+        public void onTransitionEnd(@NonNull Transition transition) {
+            hideViewWhenNotCanceled();
+            transition.removeListener(this);
+        }
+
+        @Override
+        public void onTransitionCancel(@NonNull Transition transition) {
+        }
+
+        @Override
+        public void onTransitionPause(@NonNull Transition transition) {
+            suppressLayout(false);
+        }
+
+        @Override
+        public void onTransitionResume(@NonNull Transition transition) {
+            suppressLayout(true);
+        }
+
+        private void hideViewWhenNotCanceled() {
+            if (!mCanceled) {
+                // Recreate the parent's display list in case it includes mView.
+                ViewUtils.setTransitionVisibility(mView, mFinalVisibility);
+                if (mParent != null) {
+                    mParent.invalidate();
+                }
+            }
+            // Layout is allowed now that the View is in its final state
+            suppressLayout(false);
+        }
+
+        private void suppressLayout(boolean suppress) {
+            if (mSuppressLayout && mLayoutSuppressed != suppress && mParent != null) {
+                mLayoutSuppressed = suppress;
+                ViewGroupUtils.suppressLayout(mParent, suppress);
+            }
+        }
+    }
+
+    // TODO: Implement API 23; isTransitionRequired
+
+}
diff --git a/androidx/transition/VisibilityPropagation.java b/androidx/transition/VisibilityPropagation.java
new file mode 100644
index 0000000..fd424c3
--- /dev/null
+++ b/androidx/transition/VisibilityPropagation.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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 androidx.transition;
+
+import android.view.View;
+
+/**
+ * Base class for <code>TransitionPropagation</code>s that care about
+ * View Visibility and the center position of the View.
+ */
+public abstract class VisibilityPropagation extends TransitionPropagation {
+
+    /**
+     * The property key used for {@link android.view.View#getVisibility()}.
+     */
+    private static final String PROPNAME_VISIBILITY = "android:visibilityPropagation:visibility";
+
+    /**
+     * The property key used for the center of the View in screen coordinates. This is an
+     * int[2] with the index 0 taking the x coordinate and index 1 taking the y coordinate.
+     */
+    private static final String PROPNAME_VIEW_CENTER = "android:visibilityPropagation:center";
+
+    private static final String[] VISIBILITY_PROPAGATION_VALUES = {
+            PROPNAME_VISIBILITY,
+            PROPNAME_VIEW_CENTER,
+    };
+
+    @Override
+    public void captureValues(TransitionValues values) {
+        View view = values.view;
+        Integer visibility = (Integer) values.values.get(Visibility.PROPNAME_VISIBILITY);
+        if (visibility == null) {
+            visibility = view.getVisibility();
+        }
+        values.values.put(PROPNAME_VISIBILITY, visibility);
+        int[] loc = new int[2];
+        view.getLocationOnScreen(loc);
+        loc[0] += Math.round(view.getTranslationX());
+        loc[0] += view.getWidth() / 2;
+        loc[1] += Math.round(view.getTranslationY());
+        loc[1] += view.getHeight() / 2;
+        values.values.put(PROPNAME_VIEW_CENTER, loc);
+    }
+
+    @Override
+    public String[] getPropagationProperties() {
+        return VISIBILITY_PROPAGATION_VALUES;
+    }
+
+    /**
+     * Returns {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     */
+    public int getViewVisibility(TransitionValues values) {
+        if (values == null) {
+            return View.GONE;
+        }
+        Integer visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+        if (visibility == null) {
+            return View.GONE;
+        }
+        return visibility;
+    }
+
+    /**
+     * Returns the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewX(TransitionValues values) {
+        return getViewCoordinate(values, 0);
+    }
+
+    /**
+     * Returns the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewY(TransitionValues values) {
+        return getViewCoordinate(values, 1);
+    }
+
+    private static int getViewCoordinate(TransitionValues values, int coordinateIndex) {
+        if (values == null) {
+            return -1;
+        }
+
+        int[] coordinates = (int[]) values.values.get(PROPNAME_VIEW_CENTER);
+        if (coordinates == null) {
+            return -1;
+        }
+
+        return coordinates[coordinateIndex];
+    }
+
+}
diff --git a/androidx/transition/VisibilityTest.java b/androidx/transition/VisibilityTest.java
new file mode 100644
index 0000000..8c7a2fd
--- /dev/null
+++ b/androidx/transition/VisibilityTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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 androidx.transition;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.filters.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+@MediumTest
+public class VisibilityTest extends BaseTest {
+
+    private View mView;
+    private ViewGroup mRoot;
+
+    @UiThreadTest
+    @Before
+    public void setUp() {
+        mRoot = rule.getActivity().getRoot();
+        mView = new View(rule.getActivity());
+        mRoot.addView(mView, new ViewGroup.LayoutParams(100, 100));
+    }
+
+    @Test
+    public void testMode() {
+        final CustomVisibility visibility = new CustomVisibility();
+        assertThat(visibility.getMode(), is(Visibility.MODE_IN | Visibility.MODE_OUT));
+        visibility.setMode(Visibility.MODE_IN);
+        assertThat(visibility.getMode(), is(Visibility.MODE_IN));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCustomVisibility() {
+        final CustomVisibility visibility = new CustomVisibility();
+        assertThat(visibility.getName(), is(equalTo(CustomVisibility.class.getName())));
+        assertNotNull(visibility.getTransitionProperties());
+
+        // Capture start values
+        mView.setScaleX(0.5f);
+        final TransitionValues startValues = new TransitionValues();
+        startValues.view = mView;
+        visibility.captureStartValues(startValues);
+        assertThat((float) startValues.values.get(CustomVisibility.PROPNAME_SCALE_X), is(0.5f));
+
+        // Hide the view and capture end values
+        mView.setVisibility(View.GONE);
+        final TransitionValues endValues = new TransitionValues();
+        endValues.view = mView;
+        visibility.captureEndValues(endValues);
+
+        // This should invoke onDisappear, not onAppear
+        ObjectAnimator animator = (ObjectAnimator) visibility
+                .createAnimator(mRoot, startValues, endValues);
+        assertNotNull(animator);
+        assertThat(animator.getPropertyName(), is(equalTo("scaleX")));
+
+        // Jump to the end of the animation
+        animator.end();
+
+        // This value confirms that onDisappear, not onAppear, was called
+        assertThat((float) animator.getAnimatedValue(), is(0.25f));
+    }
+
+    @Test
+    @UiThreadTest
+    public void testCustomVisibility2() {
+        final CustomVisibility2 visibility = new CustomVisibility2();
+        final TransitionValues startValues = new TransitionValues();
+        startValues.view = mView;
+        visibility.captureStartValues(startValues);
+        mView.setVisibility(View.GONE);
+        final TransitionValues endValues = new TransitionValues();
+        endValues.view = mView;
+        visibility.captureEndValues(endValues);
+        ObjectAnimator animator = (ObjectAnimator) visibility
+                .createAnimator(mRoot, startValues, endValues);
+        assertNotNull(animator);
+
+        // Jump to the end of the animation
+        animator.end();
+
+        // This value confirms that onDisappear, not onAppear, was called
+        assertThat((float) animator.getAnimatedValue(), is(0.25f));
+    }
+
+    /**
+     * A custom {@link Visibility} with 5-arg onAppear/Disappear
+     */
+    public static class CustomVisibility extends Visibility {
+
+        static final String PROPNAME_SCALE_X = "customVisibility:scaleX";
+
+        private static String[] sTransitionProperties;
+
+        @Nullable
+        @Override
+        public String[] getTransitionProperties() {
+            if (sTransitionProperties == null) {
+                String[] properties = super.getTransitionProperties();
+                if (properties != null) {
+                    sTransitionProperties = Arrays.copyOf(properties, properties.length + 1);
+                } else {
+                    sTransitionProperties = new String[1];
+                }
+                sTransitionProperties[sTransitionProperties.length - 1] = PROPNAME_SCALE_X;
+            }
+            return sTransitionProperties;
+        }
+
+        @Override
+        public void captureStartValues(@NonNull TransitionValues transitionValues) {
+            super.captureStartValues(transitionValues);
+            transitionValues.values.put(PROPNAME_SCALE_X, transitionValues.view.getScaleX());
+        }
+
+        @Override
+        public Animator onAppear(ViewGroup sceneRoot, TransitionValues startValues,
+                int startVisibility, TransitionValues endValues, int endVisibility) {
+            if (startValues == null) {
+                return null;
+            }
+            float startScaleX = (float) startValues.values.get(PROPNAME_SCALE_X);
+            return ObjectAnimator.ofFloat(startValues.view, "scaleX", startScaleX, 0.75f);
+        }
+
+        @Override
+        public Animator onDisappear(ViewGroup sceneRoot, TransitionValues startValues,
+                int startVisibility, TransitionValues endValues, int endVisibility) {
+            if (startValues == null) {
+                return null;
+            }
+            float startScaleX = (float) startValues.values.get(PROPNAME_SCALE_X);
+            return ObjectAnimator.ofFloat(startValues.view, "scaleX", startScaleX, 0.25f);
+        }
+
+    }
+
+    /**
+     * A custom {@link Visibility} with 4-arg onAppear/Disappear
+     */
+    public static class CustomVisibility2 extends Visibility {
+
+        static final String PROPNAME_SCALE_X = "customVisibility:scaleX";
+
+        @Override
+        public void captureStartValues(@NonNull TransitionValues transitionValues) {
+            super.captureStartValues(transitionValues);
+            transitionValues.values.put(PROPNAME_SCALE_X, transitionValues.view.getScaleX());
+        }
+
+        @Override
+        public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            float startScaleX = startValues == null ? 0.25f :
+                    (float) startValues.values.get(PROPNAME_SCALE_X);
+            return ObjectAnimator.ofFloat(view, "scaleX", startScaleX, 0.75f);
+        }
+
+        @Override
+        public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+                TransitionValues endValues) {
+            if (startValues == null) {
+                return null;
+            }
+            float startScaleX = (float) startValues.values.get(PROPNAME_SCALE_X);
+            return ObjectAnimator.ofFloat(view, "scaleX", startScaleX, 0.25f);
+        }
+
+    }
+
+}
diff --git a/androidx/transition/WindowIdApi14.java b/androidx/transition/WindowIdApi14.java
new file mode 100644
index 0000000..6a9231e
--- /dev/null
+++ b/androidx/transition/WindowIdApi14.java
@@ -0,0 +1,38 @@
+/*
+ * 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 androidx.transition;
+
+import android.os.IBinder;
+
+class WindowIdApi14 implements WindowIdImpl {
+
+    private final IBinder mToken;
+
+    WindowIdApi14(IBinder token) {
+        mToken = token;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof WindowIdApi14 && ((WindowIdApi14) o).mToken.equals(this.mToken);
+    }
+
+    @Override
+    public int hashCode() {
+        return mToken.hashCode();
+    }
+}
diff --git a/androidx/transition/WindowIdApi18.java b/androidx/transition/WindowIdApi18.java
new file mode 100644
index 0000000..cceaab9
--- /dev/null
+++ b/androidx/transition/WindowIdApi18.java
@@ -0,0 +1,42 @@
+/*
+ * 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 androidx.transition;
+
+import android.view.View;
+import android.view.WindowId;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+@RequiresApi(18)
+class WindowIdApi18 implements WindowIdImpl {
+
+    private final WindowId mWindowId;
+
+    WindowIdApi18(@NonNull View view) {
+        mWindowId = view.getWindowId();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        return o instanceof WindowIdApi18 && ((WindowIdApi18) o).mWindowId.equals(mWindowId);
+    }
+
+    @Override
+    public int hashCode() {
+        return mWindowId.hashCode();
+    }
+}
diff --git a/androidx/transition/WindowIdImpl.java b/androidx/transition/WindowIdImpl.java
new file mode 100644
index 0000000..ca57588
--- /dev/null
+++ b/androidx/transition/WindowIdImpl.java
@@ -0,0 +1,20 @@
+/*
+ * 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 androidx.transition;
+
+interface WindowIdImpl {
+}