Fix CollapsingToolbarLayout with child margins

CTL doesn't currently handle pinnable children with
top/bottom margins very well. This CL fixes it.

BUG: 29497596
Change-Id: If7799b28aaec6648d5add7e482862a0683f3ad3e
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 68ce5d0..e354989 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -48,6 +48,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
+import static android.support.design.widget.MathUtils.constrain;
 import static android.support.design.widget.ViewUtils.objectEquals;
 
 /**
@@ -411,21 +412,18 @@
                         == ViewCompat.LAYOUT_DIRECTION_RTL;
 
                 // Update the collapsed bounds
-                int bottomOffset = 0;
-                if (mToolbarDirectChild != null && mToolbarDirectChild != this) {
-                    final LayoutParams lp = (LayoutParams) mToolbarDirectChild.getLayoutParams();
-                    bottomOffset = lp.bottomMargin;
-                }
+                final int maxOffset = getMaxOffsetForPinChild(
+                        mToolbarDirectChild != null ? mToolbarDirectChild : mToolbar);
                 ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
                 mCollapsingTextHelper.setCollapsedBounds(
                         mTmpRect.left + (isRtl
                                 ? mToolbar.getTitleMarginEnd()
                                 : mToolbar.getTitleMarginStart()),
-                        bottom + mToolbar.getTitleMarginTop() - mTmpRect.height() - bottomOffset,
+                        mTmpRect.top + maxOffset + mToolbar.getTitleMarginTop(),
                         mTmpRect.right + (isRtl
                                 ? mToolbar.getTitleMarginStart()
                                 : mToolbar.getTitleMarginEnd()),
-                        bottom - bottomOffset - mToolbar.getTitleMarginBottom());
+                        mTmpRect.bottom + maxOffset - mToolbar.getTitleMarginBottom());
 
                 // Update the expanded bounds
                 mCollapsingTextHelper.setExpandedBounds(
@@ -1197,6 +1195,12 @@
         }
     }
 
+    final int getMaxOffsetForPinChild(View child) {
+        final ViewOffsetHelper offsetHelper = getViewOffsetHelper(child);
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        return getHeight() - (offsetHelper.getLayoutTop() + child.getHeight() + lp.bottomMargin);
+    }
+
     private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
         @Override
         public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
@@ -1211,9 +1215,8 @@
 
                 switch (lp.mCollapseMode) {
                     case LayoutParams.COLLAPSE_MODE_PIN:
-                        if (getHeight() - insetTop + verticalOffset >= child.getHeight()) {
-                            offsetHelper.setTopAndBottomOffset(-verticalOffset);
-                        }
+                        offsetHelper.setTopAndBottomOffset(
+                                constrain(-verticalOffset, 0, getMaxOffsetForPinChild(child)));
                         break;
                     case LayoutParams.COLLAPSE_MODE_PARALLAX:
                         offsetHelper.setTopAndBottomOffset(
diff --git a/design/src/android/support/design/widget/ViewOffsetHelper.java b/design/src/android/support/design/widget/ViewOffsetHelper.java
index fc16d28..088430a 100644
--- a/design/src/android/support/design/widget/ViewOffsetHelper.java
+++ b/design/src/android/support/design/widget/ViewOffsetHelper.java
@@ -91,4 +91,12 @@
     public int getLeftAndRightOffset() {
         return mOffsetLeft;
     }
+
+    public int getLayoutTop() {
+        return mLayoutTop;
+    }
+
+    public int getLayoutLeft() {
+        return mLayoutLeft;
+    }
 }
\ No newline at end of file
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_pin_margins.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_margins.xml
new file mode 100644
index 0000000..92a7268
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_margins.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/col"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.support.design.widget.AppBarLayout
+        android:id="@+id/app_bar"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/appbar_height">
+
+        <android.support.design.widget.CollapsingToolbarLayout
+            android:id="@+id/collapsing_app_bar"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            app:layout_scrollFlags="scroll">
+
+            <android.support.v7.widget.Toolbar
+                android:id="@+id/toolbar"
+                android:layout_height="?attr/actionBarSize"
+                android:layout_width="match_parent"
+                android:layout_marginTop="20dp"
+                android:layout_marginBottom="20dp"
+                app:layout_collapseMode="pin"/>
+
+        </android.support.design.widget.CollapsingToolbarLayout>
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/include_appbar_scrollview" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/values/strings.xml b/design/tests/res/values/strings.xml
index 380cbe3..f456921 100644
--- a/design/tests/res/values/strings.xml
+++ b/design/tests/res/values/strings.xml
@@ -35,6 +35,7 @@
     <string name="design_appbar_toolbar_scroll_tabs_scroll">AppBar/Toolbar Scroll + Tabs Scroll</string>
     <string name="design_appbar_toolbar_scroll_tabs_scroll_snap">AppBar/Toolbar Scroll + Tabs Scroll + Snap</string>
     <string name="design_appbar_toolbar_scroll_tabs_pin">AppBar/Toolbar Scroll + Tabs Pin</string>
+    <string name="design_appbar_collapsing_toolbar_pin_margins">AppBar/Collapsing Toolbar (pinned + margins)</string>
     <string name="design_appbar_collapsing_toolbar_with_image">AppBar/Collapsing Toolbar + Parallax Image</string>
     <string name="design_appbar_anchored_fab_margin_bottom">AppBar + anchored FAB with bottom margin</string>
     <string name="design_appbar_anchored_fab_margin_top">AppBar + anchored FAB with top margin</string>
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index 7be4801..8148720 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -381,5 +381,60 @@
                 mCollapsingToolbar.addView(view);
             }
         });
+
+    }
+
+    @Test
+    public void testPinnedToolbarWithMargins() throws Throwable {
+        configureContent(R.layout.design_appbar_toolbar_collapse_pin_margins,
+                R.string.design_appbar_collapsing_toolbar_pin_margins);
+
+        CollapsingToolbarLayout.LayoutParams toolbarLp =
+                (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+        assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+                toolbarLp.getCollapseMode());
+
+        final int[] appbarOnScreenXY = new int[2];
+        final int[] toolbarOnScreenXY = new int[2];
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
+
+        final int originalAppbarTop = appbarOnScreenXY[1];
+        final int originalAppbarBottom = originalAppbarTop + mAppBar.getHeight();
+        final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+        final int toolbarHeight = mToolbar.getHeight();
+        final int toolbarVerticalMargins = toolbarLp.topMargin + toolbarLp.bottomMargin;
+        final int appbarHeight = mAppBar.getHeight();
+
+        // Perform a swipe-up gesture across the horizontal center of the screen.
+        int swipeAmount = appbarHeight - toolbarHeight - toolbarVerticalMargins;
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + (3 * swipeAmount / 2),
+                swipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
+        // At this point the toolbar should be visually pinned to the bottom of the appbar layout,
+        // observing it's margins
+        // The toolbar should still be visually pinned to the bottom of the appbar layout
+        assertEquals(originalAppbarTop, toolbarOnScreenXY[1] - toolbarLp.topMargin, 1);
+
+        // Swipe up again, this time just 50% of the margin size
+        swipeAmount = toolbarVerticalMargins / 2;
+        performVerticalSwipeUpGesture(
+                R.id.coordinator_layout,
+                centerX,
+                originalAppbarBottom + (3 * swipeAmount / 2),
+                swipeAmount);
+
+        mAppBar.getLocationOnScreen(appbarOnScreenXY);
+        mToolbar.getLocationOnScreen(toolbarOnScreenXY);
+
+        // The toolbar should still be visually pinned to the bottom of the appbar layout
+        assertEquals(appbarOnScreenXY[1] + appbarHeight,
+                toolbarOnScreenXY[1] + toolbarHeight + toolbarLp.bottomMargin, 1);
     }
 }