Fix transitions on disappearing view hiearchies

Previously, Fade transitions did not work correctly on hirearchies; they
only handled individual views. in particular, they would side-effect all
fading views by removing them from their parent to fade them out in the
overlay of the scene root. This worked for the fade-out transition itself,
but caused problems when those same hierarchies were added back in and
another Fade was run on the hierarchy, because now all of the views inside
that parent node had been removed, so they didn't fade in at all.

The fix was to add logic in Visibility to detect when a disappearing
view was inside a hierarchy that was also disappearing, and to skip the
fade on the views inside that hierarchy, leaving only the top-most
disappearing view to be faded out, thus preserving the hierarchy under
that faded-out group.

Along the way, there were various cleanups, fixes, and refactorings in the
transition code, and slight API modifications.

Issue #9406371 Transitions: Removing view hierarchy not working correctly
Issue #9470255 Transitions: Separate different transitions by Scene Root

Change-Id: I42e80dac6097fee740f651dcc0535f2c57c11ebb
diff --git a/api/current.txt b/api/current.txt
index f884bf4..a896ecb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28547,28 +28547,30 @@
     method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
   }
 
-  public abstract class Transition {
+  public abstract class Transition implements java.lang.Cloneable {
     ctor public Transition();
     method public void addListener(android.view.transition.Transition.TransitionListener);
     method protected void cancelTransition();
     method protected abstract void captureValues(android.view.transition.TransitionValues, boolean);
+    method public android.view.transition.Transition clone();
     method public long getDuration();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.util.ArrayList<android.view.transition.Transition.TransitionListener> getListeners();
     method public long getStartDelay();
     method public int[] getTargetIds();
     method public android.view.View[] getTargets();
+    method protected android.view.transition.TransitionValues getTransitionValues(android.view.View, boolean);
     method protected void onTransitionCancel();
     method protected void onTransitionEnd();
     method protected void onTransitionStart();
     method protected abstract android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
-    method protected boolean prePlay(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
     method public void removeListener(android.view.transition.Transition.TransitionListener);
     method public android.view.transition.Transition setDuration(long);
     method public void setInterpolator(android.animation.TimeInterpolator);
     method public void setStartDelay(long);
     method public android.view.transition.Transition setTargetIds(int...);
     method public android.view.transition.Transition setTargets(android.view.View...);
+    method protected boolean setup(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
   }
 
   public static abstract interface Transition.TransitionListener {
@@ -28618,12 +28620,12 @@
 
   public abstract class Visibility extends android.view.transition.Transition {
     ctor public Visibility();
-    method protected android.animation.Animator appear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
+    method protected android.animation.Animator appear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
     method protected void captureValues(android.view.transition.TransitionValues, boolean);
-    method protected android.animation.Animator disappear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
+    method protected android.animation.Animator disappear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
     method protected android.animation.Animator play(android.view.ViewGroup, android.view.transition.TransitionValues, android.view.transition.TransitionValues);
-    method protected boolean preAppear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
-    method protected boolean preDisappear(android.view.ViewGroup, android.view.View, int, android.view.View, int);
+    method protected boolean setupAppear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
+    method protected boolean setupDisappear(android.view.ViewGroup, android.view.transition.TransitionValues, int, android.view.transition.TransitionValues, int);
   }
 
 }
diff --git a/core/java/android/view/transition/AutoTransition.java b/core/java/android/view/transition/AutoTransition.java
index d94cf2c..7ddac7e 100644
--- a/core/java/android/view/transition/AutoTransition.java
+++ b/core/java/android/view/transition/AutoTransition.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 /**
diff --git a/core/java/android/view/transition/Crossfade.java b/core/java/android/view/transition/Crossfade.java
index a40d0bf..1e9b6fa 100644
--- a/core/java/android/view/transition/Crossfade.java
+++ b/core/java/android/view/transition/Crossfade.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -139,7 +140,7 @@
     }
 
     @Override
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return false;
diff --git a/core/java/android/view/transition/Fade.java b/core/java/android/view/transition/Fade.java
index 8e4909d..f3a4a39 100644
--- a/core/java/android/view/transition/Fade.java
+++ b/core/java/android/view/transition/Fade.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -32,6 +33,8 @@
 public class Fade extends Visibility {
 
     private static final String LOG_TAG = "Fade";
+    private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
+    private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
 
     /**
      * Fading mode used in {@link #Fade(int)} to make the transition
@@ -81,8 +84,19 @@
     }
 
     @Override
-    protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected void captureValues(TransitionValues values, boolean start) {
+        super.captureValues(values, start);
+        int[] loc = new int[2];
+        values.view.getLocationOnScreen(loc);
+        values.values.put(PROPNAME_SCREEN_X, loc[0]);
+        values.values.put(PROPNAME_SCREEN_Y, loc[1]);
+    }
+
+    @Override
+    protected boolean setupAppear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View endView = (endValues != null) ? endValues.view : null;
         if ((mFadingMode & IN) != IN) {
             return false;
         }
@@ -91,27 +105,32 @@
     }
 
     @Override
-    protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected Animator appear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View endView = (endValues != null) ? endValues.view : null;
         if ((mFadingMode & IN) != IN) {
             return null;
         }
-        // TODO: hack - retain original value from before preAppear
+        // TODO: hack - retain original value from before setupAppear
         return runAnimation(endView, 0, 1, null);
         // TODO: end listener to make sure we end at 1 no matter what
     }
 
     @Override
-    protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected boolean setupDisappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
         if ((mFadingMode & OUT) != OUT) {
             return false;
         }
+        View view;
+        View startView = (startValues != null) ? startValues.view : null;
+        View endView = (endValues != null) ? endValues.view : null;
         if (Transition.DBG) {
             Log.d(LOG_TAG, "Fade.predisappear: startView, startVis, endView, endVis = " +
                         startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
         }
-        View view;
         View overlayView = null;
         View viewToKeep = null;
         if (endView == null) {
@@ -137,6 +156,12 @@
         // TODO: add automatic facility to Visibility superclass for keeping views around
         if (overlayView != null) {
             // TODO: Need to do this for general case of adding to overlay
+            int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
+            int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+            overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
             sceneRoot.getOverlay().add(overlayView);
             return true;
         }
@@ -150,11 +175,14 @@
     }
 
     @Override
-    protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected Animator disappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
         if ((mFadingMode & OUT) != OUT) {
             return null;
         }
+        View startView = (startValues != null) ? startValues.view : null;
+        View endView = (endValues != null) ? endValues.view : null;
         if (Transition.DBG) {
             Log.d(LOG_TAG, "Fade.disappear: startView, startVis, endView, endVis = " +
                 startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
diff --git a/core/java/android/view/transition/Move.java b/core/java/android/view/transition/Move.java
index 1d05419..5c9da88 100644
--- a/core/java/android/view/transition/Move.java
+++ b/core/java/android/view/transition/Move.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -212,7 +213,7 @@
     }
 
     @Override
-    protected boolean prePlay(final ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(final ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return false;
diff --git a/core/java/android/view/transition/Recolor.java b/core/java/android/view/transition/Recolor.java
index 45407e1..2179960 100644
--- a/core/java/android/view/transition/Recolor.java
+++ b/core/java/android/view/transition/Recolor.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -50,7 +51,7 @@
     }
 
     @Override
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return false;
diff --git a/core/java/android/view/transition/Rotate.java b/core/java/android/view/transition/Rotate.java
index b42fbe5..8d579d2 100644
--- a/core/java/android/view/transition/Rotate.java
+++ b/core/java/android/view/transition/Rotate.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -34,7 +35,7 @@
     }
 
     @Override
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null || endValues == null) {
             return false;
diff --git a/core/java/android/view/transition/Scene.java b/core/java/android/view/transition/Scene.java
index 62cb9d3..cf3eadb 100644
--- a/core/java/android/view/transition/Scene.java
+++ b/core/java/android/view/transition/Scene.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.content.Context;
diff --git a/core/java/android/view/transition/Slide.java b/core/java/android/view/transition/Slide.java
index 8630ee2..e39daa6 100644
--- a/core/java/android/view/transition/Slide.java
+++ b/core/java/android/view/transition/Slide.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -36,8 +37,10 @@
     private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
 
     @Override
-    protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected Animator appear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View endView = (endValues != null) ? endValues.view : null;
         ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y,
                 -2 * endView.getHeight(), 0);
         anim.setInterpolator(sDecelerator);
@@ -45,22 +48,28 @@
     }
 
     @Override
-    protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected boolean setupAppear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View endView = (endValues != null) ? endValues.view : null;
         endView.setTranslationY(-2 * endView.getHeight());
         return true;
     }
 
     @Override
-    protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected boolean setupDisappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View startView = (startValues != null) ? startValues.view : null;
         startView.setTranslationY(0);
         return true;
     }
 
     @Override
-    protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected Animator disappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        View startView = (startValues != null) ? startValues.view : null;
         ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0,
                 -2 * startView.getHeight());
         anim.setInterpolator(sAccelerator);
diff --git a/core/java/android/view/transition/TextChange.java b/core/java/android/view/transition/TextChange.java
index ac2852c..16e990f 100644
--- a/core/java/android/view/transition/TextChange.java
+++ b/core/java/android/view/transition/TextChange.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -46,7 +47,7 @@
     }
 
     @Override
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         if (startValues == null || endValues == null || !(endValues.view instanceof TextView)) {
             return false;
diff --git a/core/java/android/view/transition/Transition.java b/core/java/android/view/transition/Transition.java
index 205c825..fd12339 100644
--- a/core/java/android/view/transition/Transition.java
+++ b/core/java/android/view/transition/Transition.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
@@ -52,7 +53,7 @@
  * with TextureView because they rely on {@link ViewOverlay} functionality,
  * which does not currently work with TextureView.</p>
  */
-public abstract class Transition {
+public abstract class Transition implements Cloneable {
 
     private static final String LOG_TAG = "Transition";
     static final boolean DBG = false;
@@ -62,18 +63,14 @@
     TimeInterpolator mInterpolator = null;
     int[] mTargetIds;
     View[] mTargets;
-    private ArrayMap<View, TransitionValues> mStartValues =
-            new ArrayMap<View, TransitionValues>();
-    private SparseArray<TransitionValues> mStartIdValues = new SparseArray<TransitionValues>();
-    private LongSparseArray<TransitionValues> mStartItemIdValues =
-            new LongSparseArray<TransitionValues>();
-    private ArrayMap<View, TransitionValues> mEndValues =
-            new ArrayMap<View, TransitionValues>();
-    private SparseArray<TransitionValues> mEndIdValues = new SparseArray<TransitionValues>();
-    private LongSparseArray<TransitionValues> mEndItemIdValues =
-            new LongSparseArray<TransitionValues>();
+    private TransitionValuesMaps mStartValues = new TransitionValuesMaps();
+    private TransitionValuesMaps mEndValues = new TransitionValuesMaps();
+    TransitionGroup mParent = null;
 
-    // Used to carry data between preplay() and play(), cleared before every scene transition
+    // Scene Root is set at play() time in the cloned Transition
+    ViewGroup mSceneRoot = null;
+
+    // Used to carry data between setup() and play(), cleared before every scene transition
     private ArrayList<TransitionValues> mPlayStartValuesList = new ArrayList<TransitionValues>();
     private ArrayList<TransitionValues> mPlayEndValuesList = new ArrayList<TransitionValues>();
 
@@ -185,7 +182,7 @@
      * 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 in the preplay() method.
+     * different value should set that value in the setup() method.
      *
      * <p>Additionally, a Transition can perform logic to determine whether
      * the transition needs to run on the given target and start/end values.
@@ -206,19 +203,19 @@
      * TransitionValues, TransitionValues) play()} method should be called
      * during this scene change, false otherwise.
      */
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
         return true;
     }
 
     /**
-     * This version of prePlay() is called with the entire set of start/end
+     * This version of setup() is called with the entire set of start/end
      * values. The implementation in Transition iterates through these lists
-     * and calls {@link #prePlay(ViewGroup, TransitionValues, TransitionValues)}
+     * and calls {@link #setup(ViewGroup, TransitionValues, TransitionValues)}
      * with each set of start/end values on this transition. The
      * TransitionGroup subclass overrides this method and delegates it to
      * each of its children in succession. The intention in splitting
-     * preplay() out from play() is to allow all Transitions in the tree to
+     * setup() out from play() is to allow all Transitions in the tree to
      * set up the appropriate start scene for their target objects prior to
      * any calls to play(), which is necessary when there is a sequential
      * Transition, where a child transition which is not the first may want to
@@ -226,32 +223,29 @@
      *
      * @hide
      */
-    protected void prePlay(ViewGroup sceneRoot, ArrayMap<View, TransitionValues> startValues,
-            SparseArray<TransitionValues> startIdValues,
-            LongSparseArray<TransitionValues> startItemIdValues,
-            ArrayMap<View, TransitionValues> endValues,
-            SparseArray<TransitionValues> endIdValues,
-            LongSparseArray<TransitionValues> endItemIdValues) {
+    protected void setup(ViewGroup sceneRoot, TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues) {
         mPlayStartValuesList.clear();
         mPlayEndValuesList.clear();
-        ArrayMap<View, TransitionValues> endCopy = new ArrayMap<View, TransitionValues>(endValues);
+        ArrayMap<View, TransitionValues> endCopy =
+                new ArrayMap<View, TransitionValues>(endValues.viewValues);
         SparseArray<TransitionValues> endIdCopy =
-                new SparseArray<TransitionValues>(endIdValues.size());
-        for (int i = 0; i < endIdValues.size(); ++i) {
-            int id = endIdValues.keyAt(i);
-            endIdCopy.put(id, endIdValues.valueAt(i));
+                new SparseArray<TransitionValues>(endValues.idValues.size());
+        for (int i = 0; i < endValues.idValues.size(); ++i) {
+            int id = endValues.idValues.keyAt(i);
+            endIdCopy.put(id, endValues.idValues.valueAt(i));
         }
         LongSparseArray<TransitionValues> endItemIdCopy =
-                new LongSparseArray<TransitionValues>(endItemIdValues.size());
-        for (int i = 0; i < endItemIdValues.size(); ++i) {
-            long id = endItemIdValues.keyAt(i);
-            endItemIdCopy.put(id, endItemIdValues.valueAt(i));
+                new LongSparseArray<TransitionValues>(endValues.itemIdValues.size());
+        for (int i = 0; i < endValues.itemIdValues.size(); ++i) {
+            long id = endValues.itemIdValues.keyAt(i);
+            endItemIdCopy.put(id, endValues.itemIdValues.valueAt(i));
         }
         // Walk through the start values, playing everything we find
         // Remove from the end set as we go
         ArrayList<TransitionValues> startValuesList = new ArrayList<TransitionValues>();
         ArrayList<TransitionValues> endValuesList = new ArrayList<TransitionValues>();
-        for (View view : startValues.keySet()) {
+        for (View view : startValues.viewValues.keySet()) {
             TransitionValues start = null;
             TransitionValues end = null;
             boolean isInListView = false;
@@ -260,13 +254,13 @@
             }
             if (!isInListView) {
                 int id = view.getId();
-                start = startValues.get(view) != null ?
-                        startValues.get(view) : startIdValues.get(id);
-                if (endValues.get(view) != null) {
-                    end = endValues.get(view);
+                start = startValues.viewValues.get(view) != null ?
+                        startValues.viewValues.get(view) : startValues.idValues.get(id);
+                if (endValues.viewValues.get(view) != null) {
+                    end = endValues.viewValues.get(view);
                     endCopy.remove(view);
                 } else {
-                    end = endIdValues.get(id);
+                    end = endValues.idValues.get(id);
                     View removeView = null;
                     for (View viewToRemove : endCopy.keySet()) {
                         if (viewToRemove.getId() == id) {
@@ -287,7 +281,7 @@
                 if (parent.getAdapter().hasStableIds()) {
                     int position = parent.getPositionForView(view);
                     long itemId = parent.getItemIdAtPosition(position);
-                    start = startItemIdValues.get(itemId);
+                    start = startValues.itemIdValues.get(itemId);
                     endItemIdCopy.remove(itemId);
                     // TODO: deal with targetIDs for itemIDs for ListView items
                     startValuesList.add(start);
@@ -295,12 +289,12 @@
                 }
             }
         }
-        int startItemIdCopySize = startItemIdValues.size();
+        int startItemIdCopySize = startValues.itemIdValues.size();
         for (int i = 0; i < startItemIdCopySize; ++i) {
-            long id = startItemIdValues.keyAt(i);
+            long id = startValues.itemIdValues.keyAt(i);
             if (isValidTarget(null, id)) {
-                TransitionValues start = startItemIdValues.get(id);
-                TransitionValues end = endItemIdValues.get(id);
+                TransitionValues start = startValues.itemIdValues.get(id);
+                TransitionValues end = endValues.itemIdValues.get(id);
                 endItemIdCopy.remove(id);
                 startValuesList.add(start);
                 endValuesList.add(end);
@@ -310,8 +304,8 @@
         for (View view : endCopy.keySet()) {
             int id = view.getId();
             if (isValidTarget(view, id)) {
-                TransitionValues start = startValues.get(view) != null ?
-                        startValues.get(view) : startIdValues.get(id);
+                TransitionValues start = startValues.viewValues.get(view) != null ?
+                        startValues.viewValues.get(view) : startValues.idValues.get(id);
                 TransitionValues end = endCopy.get(view);
                 endIdCopy.remove(id);
                 startValuesList.add(start);
@@ -322,7 +316,7 @@
         for (int i = 0; i < endIdCopySize; ++i) {
             int id = endIdCopy.keyAt(i);
             if (isValidTarget(null, id)) {
-                TransitionValues start = startIdValues.get(id);
+                TransitionValues start = startValues.idValues.get(id);
                 TransitionValues end = endIdCopy.get(id);
                 startValuesList.add(start);
                 endValuesList.add(end);
@@ -332,7 +326,7 @@
         for (int i = 0; i < endItemIdCopySize; ++i) {
             long id = endItemIdCopy.keyAt(i);
             // TODO: Deal with targetIDs and itemIDs
-            TransitionValues start = startItemIdValues.get(id);
+            TransitionValues start = startValues.itemIdValues.get(id);
             TransitionValues end = endItemIdCopy.get(id);
             startValuesList.add(start);
             endValuesList.add(end);
@@ -341,7 +335,7 @@
             TransitionValues start = startValuesList.get(i);
             TransitionValues end = endValuesList.get(i);
             // TODO: what to do about targetIds and itemIds?
-            if (prePlay(sceneRoot, start, end)) {
+            if (setup(sceneRoot, start, end)) {
                 // Note: we've already done the check against targetIDs in these lists
                 mPlayStartValuesList.add(start);
                 mPlayEndValuesList.add(end);
@@ -390,13 +384,7 @@
      *
      * @hide
      */
-    protected void play(ViewGroup sceneRoot,
-            final ArrayMap<View, TransitionValues> startValues,
-            final SparseArray<TransitionValues> startIdValues,
-            final LongSparseArray<TransitionValues> startItemIdValues,
-            final ArrayMap<View, TransitionValues> endValues,
-            final SparseArray<TransitionValues> endIdValues,
-            final LongSparseArray<TransitionValues> endItemIdValues) {
+    protected void play(ViewGroup sceneRoot) {
 
         startTransition();
         // Now walk the list of TransitionValues, calling play for each pair
@@ -436,7 +424,7 @@
      * <code>start</code>. 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 preplay and play() methods to determine what, if any,
+     * later during the setup() and play() methods to determine what, if any,
      * animations, should be run.
      *
      * @param transitionValues The holder any values that the Transition
@@ -553,14 +541,14 @@
                         values.view = view;
                         captureValues(values, start);
                         if (start) {
-                            mStartValues.put(view, values);
+                            mStartValues.viewValues.put(view, values);
                             if (id >= 0) {
-                                mStartIdValues.put(id, values);
+                                mStartValues.idValues.put(id, values);
                             }
                         } else {
-                            mEndValues.put(view, values);
+                            mEndValues.viewValues.put(view, values);
                             if (id >= 0) {
-                                mEndIdValues.put(id, values);
+                                mEndValues.idValues.put(id, values);
                             }
                         }
                     }
@@ -574,9 +562,9 @@
                         values.view = view;
                         captureValues(values, start);
                         if (start) {
-                            mStartValues.put(view, values);
+                            mStartValues.viewValues.put(view, values);
                         } else {
-                            mEndValues.put(view, values);
+                            mEndValues.viewValues.put(view, values);
                         }
                     }
                 }
@@ -622,21 +610,21 @@
         captureValues(values, start);
         if (start) {
             if (!isListViewItem) {
-                mStartValues.put(view, values);
+                mStartValues.viewValues.put(view, values);
                 if (id >= 0) {
-                    mStartIdValues.put((int) id, values);
+                    mStartValues.idValues.put((int) id, values);
                 }
             } else {
-                mStartItemIdValues.put(id, values);
+                mStartValues.itemIdValues.put(id, values);
             }
         } else {
             if (!isListViewItem) {
-                mEndValues.put(view, values);
+                mEndValues.viewValues.put(view, values);
                 if (id >= 0) {
-                    mEndIdValues.put((int) id, values);
+                    mEndValues.idValues.put((int) id, values);
                 }
             } else {
-                mEndItemIdValues.put(id, values);
+                mEndValues.itemIdValues.put(id, values);
             }
         }
         if (view instanceof ViewGroup) {
@@ -648,18 +636,45 @@
     }
 
     /**
+     * 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.
+     */
+    protected TransitionValues getTransitionValues(View view, boolean start) {
+        if (mParent != null) {
+            return mParent.getTransitionValues(view, start);
+        }
+        TransitionValuesMaps valuesMaps = start ? mStartValues : mEndValues;
+        TransitionValues values = valuesMaps.viewValues.get(view);
+        if (values == null) {
+            int id = view.getId();
+            if (id >= 0) {
+                values = valuesMaps.idValues.get(id);
+            }
+            if (values == null && view.getParent() instanceof ListView) {
+                ListView listview = (ListView) view.getParent();
+                int position = listview.getPositionForView(view);
+                long itemId = listview.getItemIdAtPosition(position);
+                values = valuesMaps.itemIdValues.get(itemId);
+            }
+            // TODO: Doesn't handle the case where a view was parented to a
+            // ListView (with an itemId), but no longer is
+        }
+        return values;
+    }
+
+    /**
      * Called by TransitionManager to play the transition. This calls
-     * prePlay() and then play() with the full set of per-view
+     * setup() and then play() with the full set of per-view
      * transitionValues objects
      */
-    void play(ViewGroup sceneRoot) {
-        // prePlay() must be called on entire transition hierarchy and set of views
+    void playTransition(ViewGroup sceneRoot) {
+        // setup() must be called on entire transition hierarchy and set of views
         // before calling play() on anything; every transition needs a chance to set up
         // target views appropriately before transitions begin running
-        prePlay(sceneRoot, mStartValues, mStartIdValues, mStartItemIdValues,
-                mEndValues, mEndIdValues, mEndItemIdValues);
-        play(sceneRoot, mStartValues, mStartIdValues, mStartItemIdValues,
-                mEndValues, mEndIdValues, mEndItemIdValues);
+        setup(sceneRoot, mStartValues, mEndValues);
+        play(sceneRoot);
     }
 
     /**
@@ -766,26 +781,26 @@
                     tmpListeners.get(i).onTransitionEnd(this);
                 }
             }
-            for (int i = 0; i < mStartItemIdValues.size(); ++i) {
-                TransitionValues tv = mStartItemIdValues.valueAt(i);
+            for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
+                TransitionValues tv = mStartValues.itemIdValues.valueAt(i);
                 View v = tv.view;
                 if (v.hasTransientState()) {
                     v.setHasTransientState(false);
                 }
             }
-            for (int i = 0; i < mEndItemIdValues.size(); ++i) {
-                TransitionValues tv = mEndItemIdValues.valueAt(i);
+            for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
+                TransitionValues tv = mEndValues.itemIdValues.valueAt(i);
                 View v = tv.view;
                 if (v.hasTransientState()) {
                     v.setHasTransientState(false);
                 }
             }
-            mStartValues.clear();
-            mStartIdValues.clear();
-            mStartItemIdValues.clear();
-            mEndValues.clear();
-            mEndIdValues.clear();
-            mEndItemIdValues.clear();
+            mStartValues.viewValues.clear();
+            mStartValues.idValues.clear();
+            mStartValues.itemIdValues.clear();
+            mEndValues.viewValues.clear();
+            mEndValues.idValues.clear();
+            mEndValues.itemIdValues.clear();
             mCurrentAnimators.clear();
         }
     }
@@ -853,11 +868,25 @@
         return mListeners;
     }
 
+    void setSceneRoot(ViewGroup sceneRoot) {
+        mSceneRoot = sceneRoot;
+    }
+
     @Override
     public String toString() {
         return toString("");
     }
 
+    @Override
+    public Transition clone() {
+        Transition clone = null;
+        try {
+            clone = (Transition) super.clone();
+        } catch (CloneNotSupportedException e) {}
+
+        return clone;
+    }
+
     String toString(String indent) {
         String result = indent + getClass().getSimpleName() + "@" +
                 Integer.toHexString(hashCode()) + ": ";
diff --git a/core/java/android/view/transition/TransitionGroup.java b/core/java/android/view/transition/TransitionGroup.java
index 8ff46b6..6f9a3ef 100644
--- a/core/java/android/view/transition/TransitionGroup.java
+++ b/core/java/android/view/transition/TransitionGroup.java
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
 import android.util.AndroidRuntimeException;
-import android.util.ArrayMap;
-import android.util.LongSparseArray;
-import android.util.SparseArray;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -106,6 +104,7 @@
             int numTransitions = transitions.length;
             for (int i = 0; i < numTransitions; ++i) {
                 mTransitions.add(transitions[i]);
+                transitions[i].mParent = this;
                 if (mDuration >= 0) {
                     transitions[0].setDuration(mDuration);
                 }
@@ -139,6 +138,7 @@
      */
     public void removeTransition(Transition transition) {
         mTransitions.remove(transition);
+        transition.mParent = null;
     }
 
     /**
@@ -147,8 +147,9 @@
      * must finish first).
      */
     private void setupStartEndListeners() {
+        TransitionGroupListener listener = new TransitionGroupListener(this);
         for (Transition childTransition : mTransitions) {
-            childTransition.addListener(mListener);
+            childTransition.addListener(listener);
         }
         mCurrentListeners = mTransitions.size();
     }
@@ -157,55 +158,47 @@
      * This listener is used to detect when all child transitions are done, at
      * which point this transition group is also done.
      */
-    private TransitionListener mListener = new TransitionListenerAdapter() {
+    static class TransitionGroupListener extends TransitionListenerAdapter {
+        TransitionGroup mTransitionGroup;
+        TransitionGroupListener(TransitionGroup transitionGroup) {
+            mTransitionGroup = transitionGroup;
+        }
         @Override
         public void onTransitionStart(Transition transition) {
-            if (!mStarted) {
-                startTransition();
-                mStarted = true;
+            if (!mTransitionGroup.mStarted) {
+                mTransitionGroup.startTransition();
+                mTransitionGroup.mStarted = true;
             }
         }
 
         @Override
         public void onTransitionEnd(Transition transition) {
-            --mCurrentListeners;
-            if (mCurrentListeners == 0) {
+            --mTransitionGroup.mCurrentListeners;
+            if (mTransitionGroup.mCurrentListeners == 0) {
                 // All child trans
-                mStarted = false;
-                endTransition();
+                mTransitionGroup.mStarted = false;
+                mTransitionGroup.endTransition();
             }
             transition.removeListener(this);
         }
-    };
-
-    /**
-     * @hide
-     */
-    @Override
-    protected void prePlay(ViewGroup sceneRoot,
-            ArrayMap<View, TransitionValues> startValues,
-            SparseArray<TransitionValues> startIdValues,
-            LongSparseArray<TransitionValues> startItemIdValues,
-            ArrayMap<View, TransitionValues> endValues,
-            SparseArray<TransitionValues> endIdValues,
-            LongSparseArray<TransitionValues> endItemIdValues) {
-        for (Transition childTransition : mTransitions) {
-            childTransition.prePlay(sceneRoot, startValues, startIdValues, startItemIdValues,
-                    endValues, endIdValues, endItemIdValues);
-        }
     }
 
     /**
      * @hide
      */
     @Override
-    protected void play(ViewGroup sceneRoot,
-            final ArrayMap<View, TransitionValues> startValues,
-            final SparseArray<TransitionValues> startIdValues,
-            final LongSparseArray<TransitionValues> startItemIdValues,
-            final ArrayMap<View, TransitionValues> endValues,
-            final SparseArray<TransitionValues> endIdValues,
-            final LongSparseArray<TransitionValues> endItemIdValues) {
+    protected void setup(ViewGroup sceneRoot, TransitionValuesMaps startValues,
+            TransitionValuesMaps endValues) {
+        for (Transition childTransition : mTransitions) {
+            childTransition.setup(sceneRoot, startValues, endValues);
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    protected void play(ViewGroup sceneRoot) {
         setupStartEndListeners();
         final ViewGroup finalSceneRoot = sceneRoot;
         if (!mPlayTogether) {
@@ -217,22 +210,18 @@
                 previousTransition.addListener(new TransitionListenerAdapter() {
                     @Override
                     public void onTransitionEnd(Transition transition) {
-                        nextTransition.play(finalSceneRoot,
-                                startValues, startIdValues, startItemIdValues,
-                                endValues, endIdValues, endItemIdValues);
+                        nextTransition.play(finalSceneRoot);
                         transition.removeListener(this);
                     }
                 });
             }
             Transition firstTransition = mTransitions.get(0);
             if (firstTransition != null) {
-                firstTransition.play(finalSceneRoot, startValues, startIdValues, startItemIdValues,
-                        endValues, endIdValues, endItemIdValues);
+                firstTransition.play(finalSceneRoot);
             }
         } else {
             for (Transition childTransition : mTransitions) {
-                childTransition.play(finalSceneRoot, startValues, startIdValues, startItemIdValues,
-                        endValues, endIdValues, endItemIdValues);
+                childTransition.play(finalSceneRoot);
             }
         }
     }
@@ -312,6 +301,15 @@
     }
 
     @Override
+    void setSceneRoot(ViewGroup sceneRoot) {
+        super.setSceneRoot(sceneRoot);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setSceneRoot(sceneRoot);
+        }
+    }
+
+    @Override
     String toString(String indent) {
         String result = super.toString(indent);
         for (int i = 0; i < mTransitions.size(); ++i) {
@@ -320,4 +318,15 @@
         return result;
     }
 
+    @Override
+    public TransitionGroup clone() {
+        TransitionGroup clone = (TransitionGroup) super.clone();
+        clone.mTransitions = new ArrayList<Transition>();
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            clone.mTransitions.add((Transition) mTransitions.get(i).clone());
+        }
+        return clone;
+    }
+
 }
diff --git a/core/java/android/view/transition/TransitionInflater.java b/core/java/android/view/transition/TransitionInflater.java
index dc930d5..be658af 100644
--- a/core/java/android/view/transition/TransitionInflater.java
+++ b/core/java/android/view/transition/TransitionInflater.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.content.Context;
diff --git a/core/java/android/view/transition/TransitionManager.java b/core/java/android/view/transition/TransitionManager.java
index 8088f6b..59b07b1 100644
--- a/core/java/android/view/transition/TransitionManager.java
+++ b/core/java/android/view/transition/TransitionManager.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.util.ArrayMap;
@@ -142,15 +143,18 @@
      * @param scene The scene being entered
      * @param transition The transition to play for this scene change
      */
-    private static void changeScene(Scene scene, final Transition transition) {
+    private static void changeScene(Scene scene, Transition transition) {
 
         final ViewGroup sceneRoot = scene.getSceneRoot();
 
-        sceneChangeSetup(sceneRoot, transition);
+        Transition transitionClone = transition.clone();
+        transitionClone.setSceneRoot(sceneRoot);
+
+        sceneChangeSetup(sceneRoot, transitionClone);
 
         scene.enter();
 
-        sceneChangeRunTransition(sceneRoot, transition);
+        sceneChangeRunTransition(sceneRoot, transitionClone);
     }
 
     private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
@@ -169,7 +173,7 @@
                         }
                     });
                     transition.captureValues(sceneRoot, false);
-                    transition.play(sceneRoot);
+                    transition.playTransition(sceneRoot);
                     return true;
                 }
             });
@@ -317,7 +321,7 @@
             if (transition == null) {
                 transition = sDefaultTransition;
             }
-            final Transition finalTransition = transition;
+            final Transition finalTransition = transition.clone();
             sceneChangeSetup(sceneRoot, transition);
             sceneRoot.setCurrentScene(null);
             sceneRoot.postOnAnimation(new Runnable() {
diff --git a/core/java/android/view/transition/TransitionValues.java b/core/java/android/view/transition/TransitionValues.java
index 1ef6bf4..963e04d 100644
--- a/core/java/android/view/transition/TransitionValues.java
+++ b/core/java/android/view/transition/TransitionValues.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.util.ArrayMap;
@@ -36,7 +37,7 @@
  * 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 during the play phase of the scene change,
- * for {@link Transition#prePlay(ViewGroup, TransitionValues, TransitionValues)} and
+ * for {@link Transition#setup(ViewGroup, TransitionValues, TransitionValues)} and
  * for {@link Transition#play(ViewGroup, TransitionValues, TransitionValues)}.</p>
  */
 public class TransitionValues {
@@ -55,7 +56,10 @@
     public String toString() {
         String returnValue = "TransitionValues@" + Integer.toHexString(hashCode()) + ":\n";
         returnValue += "    view = " + view + "\n";
-        returnValue += "    values = " + values + "\n";
+        returnValue += "    values:";
+        for (String s : values.keySet()) {
+            returnValue += "    " + s + ": " + values.get(s) + "\n";
+        }
         return returnValue;
     }
 }
\ No newline at end of file
diff --git a/core/java/android/view/transition/TransitionValuesMaps.java b/core/java/android/view/transition/TransitionValuesMaps.java
new file mode 100644
index 0000000..4cfce4d
--- /dev/null
+++ b/core/java/android/view/transition/TransitionValuesMaps.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.transition;
+
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.View;
+
+class TransitionValuesMaps {
+    ArrayMap<View, TransitionValues> viewValues =
+            new ArrayMap<View, TransitionValues>();
+    SparseArray<TransitionValues> idValues = new SparseArray<TransitionValues>();
+    LongSparseArray<TransitionValues> itemIdValues =
+            new LongSparseArray<TransitionValues>();
+}
diff --git a/core/java/android/view/transition/Visibility.java b/core/java/android/view/transition/Visibility.java
index a3e6e77..c9dba6b 100644
--- a/core/java/android/view/transition/Visibility.java
+++ b/core/java/android/view/transition/Visibility.java
@@ -13,11 +13,13 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package android.view.transition;
 
 import android.animation.Animator;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 
 /**
  * This transition tracks changes to the visibility of target views in the
@@ -27,16 +29,28 @@
  * 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 more of the methods
- * {@link #preAppear(ViewGroup, View, int, View, int)},
- * {@link #preDisappear(ViewGroup, View, int, View, int)},
- * {@link #appear(ViewGroup, View, int, View, int)}, and
- * {@link #disappear(ViewGroup, View, int, View, int)}.
+ * {@link #setupAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #setupDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #appear(ViewGroup, TransitionValues, int, TransitionValues, int)}, and
+ * {@link #disappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
  */
 public abstract class Visibility extends Transition {
 
     private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
     private static final String PROPNAME_PARENT = "android:visibility:parent";
 
+    private static class VisibilityInfo {
+        boolean visibilityChange;
+        boolean fadeIn;
+        int startVisibility;
+        int endVisibility;
+        View startParent;
+        View endParent;
+    }
+
+    // Temporary structure, used in calculating state in setup() and play()
+    private VisibilityInfo mTmpVisibilityInfo = new VisibilityInfo();
+
     @Override
     protected void captureValues(TransitionValues values, boolean start) {
         int visibility = values.view.getVisibility();
@@ -44,138 +58,121 @@
         values.values.put(PROPNAME_PARENT, values.view.getParent());
     }
 
-    @Override
-    protected boolean prePlay(ViewGroup sceneRoot, TransitionValues startValues,
+    private boolean isHierarchyVisibilityChanging(ViewGroup sceneRoot, ViewGroup view) {
+
+        if (view == sceneRoot) {
+            return false;
+        }
+        TransitionValues startValues = getTransitionValues(view, true);
+        TransitionValues endValues = getTransitionValues(view, false);
+
+        if (startValues == null || endValues == null) {
+            return true;
+        }
+        int startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
+        View startParent = (View) startValues.values.get(PROPNAME_PARENT);
+        int endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
+        View endParent = (View) endValues.values.get(PROPNAME_PARENT);
+        if (startVisibility != endVisibility || startParent != endParent) {
+            return true;
+        }
+
+        ViewParent parent = view.getParent();
+        if (parent instanceof ViewGroup && parent != sceneRoot) {
+            return isHierarchyVisibilityChanging(sceneRoot, (ViewGroup) parent);
+        }
+        return false;
+    }
+
+    private VisibilityInfo getVisibilityChangeInfo(TransitionValues startValues,
             TransitionValues endValues) {
-        boolean visibilityChange = false;
-        boolean fadeIn = false;
-        int startVisibility, endVisibility;
-        View startParent, endParent;
+        final VisibilityInfo visInfo = mTmpVisibilityInfo;
+        visInfo.visibilityChange = false;
+        visInfo.fadeIn = false;
         if (startValues != null) {
-            startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
-            startParent = (View) startValues.values.get(PROPNAME_PARENT);
+            visInfo.startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
+            visInfo.startParent = (View) startValues.values.get(PROPNAME_PARENT);
         } else {
-            startVisibility = -1;
-            startParent = null;
+            visInfo.startVisibility = -1;
+            visInfo.startParent = null;
         }
         if (endValues != null) {
-            endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
-            endParent = (View) endValues.values.get(PROPNAME_PARENT);
+            visInfo.endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
+            visInfo.endParent = (View) endValues.values.get(PROPNAME_PARENT);
         } else {
-            endVisibility = -1;
-            endParent = null;
+            visInfo.endVisibility = -1;
+            visInfo.endParent = null;
         }
-        boolean existenceChange = false;
         if (startValues != null && endValues != null) {
-            if (startVisibility == endVisibility && startParent == endParent) {
-                return false;
+            if (visInfo.startVisibility == visInfo.endVisibility &&
+                    visInfo.startParent == visInfo.endParent) {
+                return visInfo;
             } else {
-                if (startVisibility != endVisibility) {
-                    if (startVisibility == View.VISIBLE) {
-                        fadeIn = false;
-                        visibilityChange = true;
-                    } else if (endVisibility == View.VISIBLE) {
-                        fadeIn = true;
-                        visibilityChange = true;
+                if (visInfo.startVisibility != visInfo.endVisibility) {
+                    if (visInfo.startVisibility == View.VISIBLE) {
+                        visInfo.fadeIn = false;
+                        visInfo.visibilityChange = true;
+                    } else if (visInfo.endVisibility == View.VISIBLE) {
+                        visInfo.fadeIn = true;
+                        visInfo.visibilityChange = true;
                     }
                     // no visibilityChange if going between INVISIBLE and GONE
-                } else if (startParent != endParent) {
-                    existenceChange = true;
-                    if (endParent == null) {
-                        fadeIn = false;
-                        visibilityChange = true;
-                    } else if (startParent == null) {
-                        fadeIn = true;
-                        visibilityChange = true;
+                } else if (visInfo.startParent != visInfo.endParent) {
+                    if (visInfo.endParent == null) {
+                        visInfo.fadeIn = false;
+                        visInfo.visibilityChange = true;
+                    } else if (visInfo.startParent == null) {
+                        visInfo.fadeIn = true;
+                        visInfo.visibilityChange = true;
                     }
                 }
             }
         }
         if (startValues == null) {
-            existenceChange = true;
-            fadeIn = true;
-            visibilityChange = true;
+            visInfo.fadeIn = true;
+            visInfo.visibilityChange = true;
         } else if (endValues == null) {
-            existenceChange = true;
-            fadeIn = false;
-            visibilityChange = true;
+            visInfo.fadeIn = false;
+            visInfo.visibilityChange = true;
         }
-        if (visibilityChange) {
-            if (fadeIn) {
-                return preAppear(sceneRoot, existenceChange ? null : startValues.view,
-                        startVisibility, endValues.view, endVisibility);
-            } else {
-                return preDisappear(sceneRoot, startValues.view, startVisibility,
-                        existenceChange ? null : endValues.view, endVisibility);
+        return visInfo;
+    }
+
+    @Override
+    protected boolean setup(ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
+        // Ensure not in parent hierarchy that's also becoming visible/invisible
+        if (visInfo.visibilityChange) {
+            ViewGroup parent = (ViewGroup) ((visInfo.endParent != null) ?
+                    visInfo.endParent : visInfo.startParent);
+            if (parent != null) {
+                if (!isHierarchyVisibilityChanging(sceneRoot, parent)) {
+                    if (visInfo.fadeIn) {
+                        return setupAppear(sceneRoot, startValues, visInfo.startVisibility,
+                                endValues, visInfo.endVisibility);
+                    } else {
+                        return setupDisappear(sceneRoot, startValues, visInfo.startVisibility,
+                                endValues, visInfo.endVisibility
+                        );
+                    }
+                }
             }
-        } else {
-            return false;
         }
+        return false;
     }
 
     @Override
     protected Animator play(ViewGroup sceneRoot, TransitionValues startValues,
             TransitionValues endValues) {
-        boolean visibilityChange = false;
-        boolean fadeIn = false;
-        int startVisibility, endVisibility;
-        View startParent, endParent;
-        if (startValues != null) {
-            startVisibility = (Integer) startValues.values.get(PROPNAME_VISIBILITY);
-            startParent = (View) startValues.values.get(PROPNAME_PARENT);
-        } else {
-            startVisibility = -1;
-            startParent = null;
-        }
-        if (endValues != null) {
-            endVisibility = (Integer) endValues.values.get(PROPNAME_VISIBILITY);
-            endParent = (View) endValues.values.get(PROPNAME_PARENT);
-        } else {
-            endVisibility = -1;
-            endParent = null;
-        }
-        boolean existenceChange = false;
-        if (startValues != null && endValues != null) {
-            if (startVisibility == endVisibility && startParent == endParent) {
-                return null;
+        VisibilityInfo visInfo = getVisibilityChangeInfo(startValues, endValues);
+        if (visInfo.visibilityChange) {
+            if (visInfo.fadeIn) {
+                return appear(sceneRoot, startValues, visInfo.startVisibility,
+                        endValues, visInfo.endVisibility);
             } else {
-                if (startVisibility != endVisibility) {
-                    if (startVisibility == View.VISIBLE) {
-                        fadeIn = false;
-                        visibilityChange = true;
-                    } else if (endVisibility == View.VISIBLE) {
-                        fadeIn = true;
-                        visibilityChange = true;
-                    }
-                    // no visibilityChange if going between INVISIBLE and GONE
-                } else if (startParent != endParent) {
-                    existenceChange = true;
-                    if (endParent == null) {
-                        fadeIn = false;
-                        visibilityChange = true;
-                    } else if (startParent == null) {
-                        fadeIn = true;
-                        visibilityChange = true;
-                    }
-                }
-            }
-        }
-        if (startValues == null) {
-            existenceChange = true;
-            fadeIn = true;
-            visibilityChange = true;
-        } else if (endValues == null) {
-            existenceChange = true;
-            fadeIn = false;
-            visibilityChange = true;
-        }
-        if (visibilityChange) {
-            if (fadeIn) {
-                return appear(sceneRoot, existenceChange ? null : startValues.view, startVisibility,
-                        endValues.view, endVisibility);
-            } else {
-                return disappear(sceneRoot, startValues.view, startVisibility,
-                        existenceChange ? null : endValues.view, endVisibility);
+                return disappear(sceneRoot, startValues, visInfo.startVisibility,
+                        endValues, visInfo.endVisibility);
             }
         }
         return null;
@@ -187,14 +184,15 @@
      * transition starting.
      *
      * @param sceneRoot
-     * @param startView
+     * @param startValues
      * @param startVisibility
-     * @param endView
+     * @param endValues
      * @param endVisibility
      * @return
      */
-    protected boolean preAppear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected boolean setupAppear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
         return true;
     }
 
@@ -202,15 +200,17 @@
      * The default implementation of this method does nothing. Subclasses
      * should override if they need to set up anything prior to the
      * transition starting.
+     *
      * @param sceneRoot
-     * @param startView
+     * @param startValues
      * @param startVisibility
-     * @param endView
+     * @param endValues
      * @param endVisibility
      * @return
      */
-    protected boolean preDisappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) {
+    protected boolean setupDisappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
         return true;
     }
 
@@ -219,25 +219,31 @@
      * should override if they need to do anything when target objects
      * appear during the scene change.
      * @param sceneRoot
-     * @param startView
+     * @param startValues
      * @param startVisibility
-     * @param endView
+     * @param endValues
      * @param endVisibility
      */
-    protected Animator appear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) { return null; }
+    protected Animator appear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        return null;
+    }
 
     /**
      * The default implementation of this method does nothing. Subclasses
      * should override if they need to do anything when target objects
      * disappear during the scene change.
      * @param sceneRoot
-     * @param startView
+     * @param startValues
      * @param startVisibility
-     * @param endView
+     * @param endValues
      * @param endVisibility
      */
-    protected Animator disappear(ViewGroup sceneRoot, View startView, int startVisibility,
-            View endView, int endVisibility) { return null; }
+    protected Animator disappear(ViewGroup sceneRoot,
+            TransitionValues startValues, int startVisibility,
+            TransitionValues endValues, int endVisibility) {
+        return null;
+    }
 
 }
diff --git a/tests/TransitionTests/AndroidManifest.xml b/tests/TransitionTests/AndroidManifest.xml
index 98174ab..3861164 100644
--- a/tests/TransitionTests/AndroidManifest.xml
+++ b/tests/TransitionTests/AndroidManifest.xml
@@ -219,6 +219,13 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:label="FadingHierachy"
+                  android:name=".FadingHierarchy">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
 
     </application>
 
diff --git a/tests/TransitionTests/res/layout/fading_hierarchy.xml b/tests/TransitionTests/res/layout/fading_hierarchy.xml
new file mode 100644
index 0000000..a24a6b6
--- /dev/null
+++ b/tests/TransitionTests/res/layout/fading_hierarchy.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:id="@+id/container"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <Button
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/submit"
+            android:onClick="sendMessage"
+            android:id="@+id/sceneSwitchButton"/>
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content">
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/button"/>
+        <LinearLayout
+                android:orientation="vertical"
+                android:id="@+id/removingContainer"
+                android:layout_width="wrap_content"
+                android:layout_height="200dip">
+            <Button
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/removingButton"
+                    android:id="@+id/removingButton"/>
+        </LinearLayout>
+        <Button
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/button"/>
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/TransitionTests/src/com/android/transitiontests/FadingHierarchy.java b/tests/TransitionTests/src/com/android/transitiontests/FadingHierarchy.java
new file mode 100644
index 0000000..e0fe379
--- /dev/null
+++ b/tests/TransitionTests/src/com/android/transitiontests/FadingHierarchy.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.transitiontests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.transition.Scene;
+import android.view.transition.TransitionManager;
+import android.widget.Button;
+
+public class FadingHierarchy extends Activity {
+
+    ViewGroup mRemovingContainer, mContainer;
+    Button mRemovingButton;
+    boolean mVisible = true;
+    ViewGroup mInnerContainerParent;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.fading_hierarchy);
+
+        mContainer = (ViewGroup) findViewById(R.id.container);
+        mRemovingContainer = (ViewGroup) findViewById(R.id.removingContainer);
+        mInnerContainerParent = (ViewGroup) mRemovingContainer.getParent();
+
+        mRemovingButton = (Button) findViewById(R.id.removingButton);
+    }
+
+    public void sendMessage(View view) {
+        TransitionManager.beginDelayedTransition(mContainer, null);
+        if (mVisible) {
+            mInnerContainerParent.removeView(mRemovingContainer);
+        } else {
+            mInnerContainerParent.addView(mRemovingContainer);
+        }
+        mVisible = !mVisible;
+    }
+}