Fix FAB not returning after Snackbar dismissal

Caused by the fact that sometimes a removed view does
not trigger a onDependentViewChanged() dispatch. As
a removal doesn't really fit that method, I've added
a onDependentViewRemoved() call to Behavior.

BUG: 21799255
Change-Id: Ib86713a38928dfdbbfcf419e44bafe8023e5a8ee
diff --git a/design/api/current.txt b/design/api/current.txt
index 27b0a30..0e638cf 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -127,6 +127,7 @@
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
     method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+    method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, V, android.view.View);
     method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);
     method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, V, int, int, int, int);
@@ -179,6 +180,7 @@
     ctor public FloatingActionButton.Behavior();
     method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
     method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+    method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
     method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
   }
 
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index e2081b7..8675e6d 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -149,6 +149,8 @@
     private boolean mDrawStatusBarBackground;
     private Drawable mStatusBarBackground;
 
+    private OnHierarchyChangeListener mOnHierarchyChangeListener;
+
     private final NestedScrollingParentHelper mNestedScrollingParentHelper =
             new NestedScrollingParentHelper(this);
 
@@ -181,6 +183,12 @@
         if (INSETS_HELPER != null) {
             INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
         }
+        super.setOnHierarchyChangeListener(new HierarchyChangeListener());
+    }
+
+    @Override
+    public void setOnHierarchyChangeListener(OnHierarchyChangeListener onHierarchyChangeListener) {
+        mOnHierarchyChangeListener = onHierarchyChangeListener;
     }
 
     @Override
@@ -1110,6 +1118,19 @@
         }
     }
 
+    void dispatchDependentViewRemoved(View removedChild) {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            final View child = getChildAt(i);
+            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+            final Behavior b = lp.getBehavior();
+
+            if (b != null && b.layoutDependsOn(this, child, removedChild)) {
+                b.onDependentViewRemoved(this, child, removedChild);
+            }
+        }
+    }
+
     /**
      * Allows the caller to manually dispatch
      * {@link Behavior#onDependentViewChanged(CoordinatorLayout, View, View)} to the associated
@@ -1734,6 +1755,23 @@
         }
 
         /**
+         * Respond to a child's dependent view being removed.
+         *
+         * <p>This method is called after a dependent view has been removed from the parent.
+         * A Behavior may use this method to appropriately update the child view in response.</p>
+         *
+         * <p>A view's dependency is determined by
+         * {@link #layoutDependsOn(CoordinatorLayout, android.view.View, android.view.View)} or
+         * if {@code child} has set another view as it's anchor.</p>
+         *
+         * @param parent the parent view of the given child
+         * @param child the child view to manipulate
+         * @param dependency the dependent view that has been removed
+         */
+        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
+        }
+
+        /**
          * Determine whether the given child view should be considered dirty.
          *
          * <p>If a property determined by the Behavior such as other dependent views would change,
@@ -2457,6 +2495,24 @@
         }
     }
 
+    final class HierarchyChangeListener implements OnHierarchyChangeListener {
+        @Override
+        public void onChildViewAdded(View parent, View child) {
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewAdded(parent, child);
+            }
+        }
+
+        @Override
+        public void onChildViewRemoved(View parent, View child) {
+            dispatchDependentViewRemoved(child);
+
+            if (mOnHierarchyChangeListener != null) {
+                mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
+            }
+        }
+    }
+
     @Override
     protected void onRestoreInstanceState(Parcelable state) {
         final SavedState ss = (SavedState) state;
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 42af049..b275174 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -351,6 +351,19 @@
             return false;
         }
 
+        @Override
+        public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
+                View dependency) {
+            if (dependency instanceof Snackbar.SnackbarLayout) {
+                // If the removed view is a SnackbarLayout, we will animate back to our normal
+                // position
+                ViewCompat.animate(child)
+                        .translationY(0f)
+                        .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
+                        .setListener(null);
+            }
+        }
+
         private boolean updateFabVisibility(CoordinatorLayout parent,
                 AppBarLayout appBarLayout, FloatingActionButton child) {
             final CoordinatorLayout.LayoutParams lp =
@@ -389,18 +402,8 @@
             if (translationY != mTranslationY) {
                 // First, cancel any current animation
                 ViewCompat.animate(fab).cancel();
-
-                if (Math.abs(translationY - mTranslationY) == snackbar.getHeight()) {
-                    // If we're travelling by the height of the Snackbar then we probably need to
-                    // animate to the value
-                    ViewCompat.animate(fab)
-                            .translationY(translationY)
-                            .setInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR)
-                            .setListener(null);
-                } else {
-                    // Else we'll set use setTranslationY
-                    ViewCompat.setTranslationY(fab, translationY);
-                }
+                // Else we'll set use setTranslationY
+                ViewCompat.setTranslationY(fab, translationY);
                 mTranslationY = translationY;
             }
         }