Fix bug in LayoutTransition for INVISIBLE views

When a view is becoming VISIBLE or INVISIBLE in a container with a
LayoutTransition, animations run to fade the view in and out and also
to run 'changing' animations on the view's other siblings. This logic
also cancels any running 'changin' animations to account for new ones
running.

However, in the specific case of INVISIBLE changes, there will be no
layout changes in the container - layout has already accounted for that
view (unlike in the case of GONE views); the visibility is just a matter of
drawing the view (or not). Therefore, we're canceling 'changing' animations
that should continue running and not replacing them with any other animations,
since new animations would only be started on layout chnages which are not
forthcoming.

One artifact seen from this bug is that the navigation bar buttons sometimes
disappear when changing orientation. This is because the menu button may
toggle between VISIBLE and INVISIBLE, causing animations on the other
buttons to get canceled, which leaves those views in a completely wrong
state.

The right thing to do is to avoid canceling in-process 'changing' animations
and to skip the logic of setting up new 'changing' animations which won't fire
anyway.

There is some minor API work in here because we did not previously have the
necessary information in LayoutTransition to know whether a view was being
hidden or shown to/from the INVISIBLE state.

Issue #5911213: LayoutTransitions ending in an odd state

Change-Id: I5c60c8583c8ea08965727b4ef17b550c40a3882c
diff --git a/api/current.txt b/api/current.txt
index 0b2ecea..2a7e2f7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2288,7 +2288,8 @@
     method public long getStagger(int);
     method public long getStartDelay(int);
     method public java.util.List<android.animation.LayoutTransition.TransitionListener> getTransitionListeners();
-    method public void hideChild(android.view.ViewGroup, android.view.View);
+    method public deprecated void hideChild(android.view.ViewGroup, android.view.View);
+    method public void hideChild(android.view.ViewGroup, android.view.View, int);
     method public boolean isChangingLayout();
     method public boolean isRunning();
     method public void removeChild(android.view.ViewGroup, android.view.View);
@@ -2300,7 +2301,8 @@
     method public void setInterpolator(int, android.animation.TimeInterpolator);
     method public void setStagger(int, long);
     method public void setStartDelay(int, long);
-    method public void showChild(android.view.ViewGroup, android.view.View);
+    method public deprecated void showChild(android.view.ViewGroup, android.view.View);
+    method public void showChild(android.view.ViewGroup, android.view.View, int);
     field public static final int APPEARING = 2; // 0x2
     field public static final int CHANGE_APPEARING = 0; // 0x0
     field public static final int CHANGE_DISAPPEARING = 1; // 0x1
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index 894f428..274a9d5 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -1024,18 +1024,25 @@
      *
      * @param parent The ViewGroup to which the View is being added.
      * @param child The View being added to the ViewGroup.
+     * @param changesLayout Whether the removal will cause changes in the layout of other views
+     * in the container. INVISIBLE views becoming VISIBLE will not cause changes and thus will not
+     * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
      */
-    public void addChild(ViewGroup parent, View child) {
+    private void addChild(ViewGroup parent, View child, boolean changesLayout) {
         // 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 (changesLayout) {
+            // 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);
             }
         }
-        runChangeTransition(parent, child, APPEARING);
+        if (changesLayout) {
+            runChangeTransition(parent, child, APPEARING);
+        }
         runAppearingTransition(parent, child);
     }
 
@@ -1048,8 +1055,31 @@
      * @param parent The ViewGroup to which the View is being added.
      * @param child The View being added to the ViewGroup.
      */
+    public void addChild(ViewGroup parent, View child) {
+        addChild(parent, child, true);
+    }
+
+    /**
+     * @deprecated Use {@link #showChild(android.view.ViewGroup, android.view.View, int)}.
+     */
+    @Deprecated
     public void showChild(ViewGroup parent, View child) {
-        addChild(parent, child);
+        addChild(parent, child, true);
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be made visible in the
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The ViewGroup in which the View is being made visible.
+     * @param child The View being made visible.
+     * @param oldVisibility The previous visibility value of the child View, either
+     * {@link View#GONE} or {@link View#INVISIBLE}.
+     */
+    public void showChild(ViewGroup parent, View child, int oldVisibility) {
+        addChild(parent, child, oldVisibility == View.GONE);
     }
 
     /**
@@ -1060,18 +1090,25 @@
      *
      * @param parent The ViewGroup from which the View is being removed.
      * @param child The View being removed from the ViewGroup.
+     * @param changesLayout Whether the removal will cause changes in the layout of other views
+     * in the container. Views becoming INVISIBLE will not cause changes and thus will not
+     * affect CHANGE_APPEARING or CHANGE_DISAPPEARING animations.
      */
-    public void removeChild(ViewGroup parent, View child) {
+    private void removeChild(ViewGroup parent, View child, boolean changesLayout) {
         // 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 (changesLayout) {
+            // 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);
             }
         }
-        runChangeTransition(parent, child, DISAPPEARING);
+        if (changesLayout) {
+            runChangeTransition(parent, child, DISAPPEARING);
+        }
         runDisappearingTransition(parent, child);
     }
 
@@ -1084,8 +1121,31 @@
      * @param parent The ViewGroup from which the View is being removed.
      * @param child The View being removed from the ViewGroup.
      */
+    public void removeChild(ViewGroup parent, View child) {
+        removeChild(parent, child, true);
+    }
+
+    /**
+     * @deprecated Use {@link #hideChild(android.view.ViewGroup, android.view.View, int)}.
+     */
+    @Deprecated
     public void hideChild(ViewGroup parent, View child) {
-        removeChild(parent, child);
+        removeChild(parent, child, true);
+    }
+
+    /**
+     * This method is called by ViewGroup when a child view is about to be hidden in
+     * container. This callback starts the process of a transition; we grab the starting
+     * values, listen for changes to all of the children of the container, and start appropriate
+     * animations.
+     *
+     * @param parent The parent ViewGroup of the View being hidden.
+     * @param child The View being hidden.
+     * @param newVisibility The new visibility value of the child View, either
+     * {@link View#GONE} or {@link View#INVISIBLE}.
+     */
+    public void hideChild(ViewGroup parent, View child, int newVisibility) {
+        removeChild(parent, child, newVisibility == View.GONE);
     }
 
     /**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8cac57d..39f603d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6822,7 +6822,8 @@
 
         if ((changed & VISIBILITY_MASK) != 0) {
             if (mParent instanceof ViewGroup) {
-                ((ViewGroup) mParent).onChildVisibilityChanged(this, (flags & VISIBILITY_MASK));
+                ((ViewGroup) mParent).onChildVisibilityChanged(this, (changed & VISIBILITY_MASK),
+                        (flags & VISIBILITY_MASK));
                 ((View) mParent).invalidate(true);
             } else if (mParent != null) {
                 mParent.invalidateChild(this, null);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index dda695fc..d906a16 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -888,18 +888,20 @@
     }
 
     /**
+     * Called when a view's visibility has changed. Notify the parent to take any appropriate
+     * action.
+     *
+     * @param child The view whose visibility has changed
+     * @param oldVisibility The previous visibility value (GONE, INVISIBLE, or VISIBLE).
+     * @param newVisibility The new visibility value (GONE, INVISIBLE, or VISIBLE).
      * @hide
-     * @param child
-     * @param visibility
      */
-    protected void onChildVisibilityChanged(View child, int visibility) {
+    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
         if (mTransition != null) {
-            if (visibility == VISIBLE) {
-                mTransition.showChild(this, child);
+            if (newVisibility == VISIBLE) {
+                mTransition.showChild(this, child, oldVisibility);
             } else {
-                mTransition.hideChild(this, child);
-            }
-            if (visibility != VISIBLE) {
+                mTransition.hideChild(this, child, newVisibility);
                 // Only track this on disappearing views - appearing views are already visible
                 // and don't need special handling during drawChild()
                 if (mVisibilityChangingChildren == null) {
@@ -914,7 +916,7 @@
 
         // in all cases, for drags
         if (mCurrentDrag != null) {
-            if (visibility == VISIBLE) {
+            if (newVisibility == VISIBLE) {
                 notifyChildOfDrag(child);
             }
         }
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 7d58011..984ec79 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -842,9 +842,11 @@
      * @hide
      */
     @Override
-    protected void onChildVisibilityChanged(View child, int visibility) {
-        super.onChildVisibilityChanged(child, visibility);
-        invalidateStructure();
+    protected void onChildVisibilityChanged(View child, int oldVisibility, int newVisibility) {
+        super.onChildVisibilityChanged(child, oldVisibility, newVisibility);
+        if (oldVisibility == GONE || newVisibility == GONE) {
+            invalidateStructure();
+        }
     }
 
     // Measurement