Cancel LayoutTransition animations selectively

A recent change to LayoutTransition caused new layout transitions to
cancel any previously-running animations. This was to handle situations
where a transition adding an item needed transitions removing items to
finish their job first (and vice versa). But canceling *all* running
animations from transitions caused some artifacts, like making the status
bar icons blink or fade in, depending on which one was started last.

The new approach is to cancel just the ones we care about: adding animations
cancel removing animations, and vice versa. Either one cancels 'changing'
animations, which prevents objects from being animated to the old end
locations, since the new transition will animate them to the correct new
end locations.

Change-Id: I68ac351b05365cace6639b6618422395c35c83fd
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 8b59554..22dd3c7 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -168,7 +168,9 @@
      */
     private final HashMap<View, Animator> pendingAnimations = new HashMap<View, Animator>();
     private final HashMap<View, Animator> currentChangingAnimations = new HashMap<View, Animator>();
-    private final HashMap<View, Animator> currentVisibilityAnimations =
+    private final HashMap<View, Animator> currentAppearingAnimations =
+            new HashMap<View, Animator>();
+    private final HashMap<View, Animator> currentDisappearingAnimations =
             new HashMap<View, Animator>();
 
     /**
@@ -709,7 +711,8 @@
      * @return true if any animations in the transition are running.
      */
     public boolean isRunning() {
-        return (currentChangingAnimations.size() > 0 || currentVisibilityAnimations.size() > 0);
+        return (currentChangingAnimations.size() > 0 || currentAppearingAnimations.size() > 0 ||
+                currentDisappearingAnimations.size() > 0);
     }
 
     /**
@@ -721,17 +724,74 @@
      * @hide
      */
     public void cancel() {
-        HashMap<View, Animator> currentAnimCopy =
-                (HashMap<View, Animator>) currentChangingAnimations.clone();
-        for (Animator anim : currentAnimCopy.values()) {
-            anim.cancel();
+        if (currentChangingAnimations.size() > 0) {
+            HashMap<View, Animator> currentAnimCopy =
+                    (HashMap<View, Animator>) currentChangingAnimations.clone();
+            for (Animator anim : currentAnimCopy.values()) {
+                anim.cancel();
+            }
+            currentChangingAnimations.clear();
         }
-        currentChangingAnimations.clear();
-        currentAnimCopy = (HashMap<View, Animator>) currentVisibilityAnimations.clone();
-        for (Animator anim : currentAnimCopy.values()) {
-            anim.end();
+        if (currentAppearingAnimations.size() > 0) {
+            HashMap<View, Animator> currentAnimCopy =
+                    (HashMap<View, Animator>) currentAppearingAnimations.clone();
+            for (Animator anim : currentAnimCopy.values()) {
+                anim.end();
+            }
+            currentAppearingAnimations.clear();
         }
-        currentVisibilityAnimations.clear();
+        if (currentDisappearingAnimations.size() > 0) {
+            HashMap<View, Animator> currentAnimCopy =
+                    (HashMap<View, Animator>) currentDisappearingAnimations.clone();
+            for (Animator anim : currentAnimCopy.values()) {
+                anim.end();
+            }
+            currentDisappearingAnimations.clear();
+        }
+    }
+
+    /**
+     * Cancels the specified type of transition. Note that we cancel() the changing animations
+     * but end() the visibility animations. This is because this method is currently called
+     * in the context of starting a new transition, so we want to move things from their mid-
+     * transition positions, but we want them to have their end-transition visibility.
+     *
+     * @hide
+     */
+    public void cancel(int transitionType) {
+        switch (transitionType) {
+            case CHANGE_APPEARING:
+            case CHANGE_DISAPPEARING:
+                if (currentChangingAnimations.size() > 0) {
+                    HashMap<View, Animator> currentAnimCopy =
+                            (HashMap<View, Animator>) currentChangingAnimations.clone();
+                    for (Animator anim : currentAnimCopy.values()) {
+                        anim.cancel();
+                    }
+                    currentChangingAnimations.clear();
+                }
+                break;
+            case APPEARING:
+                if (currentAppearingAnimations.size() > 0) {
+                    HashMap<View, Animator> currentAnimCopy =
+                            (HashMap<View, Animator>) currentAppearingAnimations.clone();
+                    for (Animator anim : currentAnimCopy.values()) {
+                        anim.end();
+                    }
+                    currentAppearingAnimations.clear();
+                }
+                break;
+            case DISAPPEARING:
+                if (currentDisappearingAnimations.size() > 0) {
+                    HashMap<View, Animator> currentAnimCopy =
+                            (HashMap<View, Animator>) currentDisappearingAnimations.clone();
+                    for (Animator anim : currentAnimCopy.values()) {
+                        anim.end();
+                    }
+                    currentDisappearingAnimations.clear();
+                }
+                break;
+        }
     }
 
     /**
@@ -741,7 +801,7 @@
      * @param child The View being added to the ViewGroup.
      */
     private void runAppearingTransition(final ViewGroup parent, final View child) {
-        Animator currentAnimation = currentVisibilityAnimations.get(child);
+        Animator currentAnimation = currentDisappearingAnimations.get(child);
         if (currentAnimation != null) {
             currentAnimation.cancel();
         }
@@ -764,14 +824,14 @@
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator anim) {
-                    currentVisibilityAnimations.remove(child);
+                    currentAppearingAnimations.remove(child);
                     for (TransitionListener listener : mListeners) {
                         listener.endTransition(LayoutTransition.this, parent, child, APPEARING);
                     }
                 }
             });
         }
-        currentVisibilityAnimations.put(child, anim);
+        currentAppearingAnimations.put(child, anim);
         anim.start();
     }
 
@@ -782,7 +842,7 @@
      * @param child The View being removed from the ViewGroup.
      */
     private void runDisappearingTransition(final ViewGroup parent, final View child) {
-        Animator currentAnimation = currentVisibilityAnimations.get(child);
+        Animator currentAnimation = currentAppearingAnimations.get(child);
         if (currentAnimation != null) {
             currentAnimation.cancel();
         }
@@ -802,7 +862,7 @@
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator anim) {
-                    currentVisibilityAnimations.remove(child);
+                    currentDisappearingAnimations.remove(child);
                     for (TransitionListener listener : mListeners) {
                         listener.endTransition(LayoutTransition.this, parent, child, DISAPPEARING);
                     }
@@ -812,7 +872,7 @@
         if (anim instanceof ObjectAnimator) {
             ((ObjectAnimator) anim).setCurrentPlayTime(0);
         }
-        currentVisibilityAnimations.put(child, anim);
+        currentDisappearingAnimations.put(child, anim);
         anim.start();
     }
 
@@ -826,9 +886,10 @@
      * @param child The View being added to the ViewGroup.
      */
     public void addChild(ViewGroup parent, View child) {
-        if (isRunning()) {
-            cancel();
-        }
+        // Want disappearing animations to finish up before proceeding
+        cancel(DISAPPEARING);
+        // Also, cancel changing animations so that we start fresh ones from current locations
+        cancel(CHANGE_APPEARING);
         if (mListeners != null) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, APPEARING);
@@ -861,9 +922,10 @@
      * @param child The View being removed from the ViewGroup.
      */
     public void removeChild(ViewGroup parent, View child) {
-        if (isRunning()) {
-            cancel();
-        }
+        // Want appearing animations to finish up before proceeding
+        cancel(APPEARING);
+        // Also, cancel changing animations so that we start fresh ones from current locations
+        cancel(CHANGE_DISAPPEARING);
         if (mListeners != null) {
             for (TransitionListener listener : mListeners) {
                 listener.startTransition(this, parent, child, DISAPPEARING);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index af4c221..3153ac5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3001,8 +3001,10 @@
     private void addViewInner(View child, int index, LayoutParams params,
             boolean preventRequestLayout) {
 
-        if (mTransition != null && mTransition.isRunning()) {
-            mTransition.cancel();
+        if (mTransition != null) {
+            // Don't prevent other add transitions from completing, but cancel remove
+            // transitions to let them complete the process before we add to the container
+            mTransition.cancel(LayoutTransition.DISAPPEARING);
         }
 
         if (child.getParent() != null) {