Fix setFitsSystemWindows not working with CoordinatorLayout

This CLs moves some of the logic around so that CoL
responds correctly to setFitsSystemWindows calls.

BUG: 29270333
Change-Id: Id1fd8db914de7c6729c74ec6740d19f538e6fdac
diff --git a/design/base/android/support/design/widget/CoordinatorLayoutInsetsHelper.java b/design/base/android/support/design/widget/CoordinatorLayoutInsetsHelper.java
deleted file mode 100644
index 1788c65..0000000
--- a/design/base/android/support/design/widget/CoordinatorLayoutInsetsHelper.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.support.v4.view.OnApplyWindowInsetsListener;
-import android.view.View;
-
-interface CoordinatorLayoutInsetsHelper {
-
-    void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener);
-
-}
diff --git a/design/lollipop/android/support/design/widget/CoordinatorLayoutInsetsHelperLollipop.java b/design/lollipop/android/support/design/widget/CoordinatorLayoutInsetsHelperLollipop.java
deleted file mode 100644
index 7f673c7..0000000
--- a/design/lollipop/android/support/design/widget/CoordinatorLayoutInsetsHelperLollipop.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2015 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.support.design.widget;
-
-import android.support.v4.view.OnApplyWindowInsetsListener;
-import android.support.v4.view.ViewCompat;
-import android.view.View;
-
-class CoordinatorLayoutInsetsHelperLollipop implements CoordinatorLayoutInsetsHelper {
-
-    @Override
-    public void setupForWindowInsets(View view, OnApplyWindowInsetsListener insetsListener) {
-        if (ViewCompat.getFitsSystemWindows(view)) {
-            // First apply the insets listener
-            ViewCompat.setOnApplyWindowInsetsListener(view, insetsListener);
-            // Now set the sys ui flags to enable us to lay out in the window insets
-            view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
-        }
-    }
-
-}
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 304cc5c..1723663 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -31,6 +31,7 @@
 import android.os.SystemClock;
 import android.support.annotation.ColorInt;
 import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.R;
 import android.support.v4.content.ContextCompat;
@@ -42,6 +43,7 @@
 import android.support.v4.view.MotionEventCompat;
 import android.support.v4.view.NestedScrollingParent;
 import android.support.v4.view.NestedScrollingParentHelper;
+import android.support.v4.view.OnApplyWindowInsetsListener;
 import android.support.v4.view.ViewCompat;
 import android.support.v4.view.WindowInsetsCompat;
 import android.text.TextUtils;
@@ -107,10 +109,8 @@
     static {
         if (Build.VERSION.SDK_INT >= 21) {
             TOP_SORTED_CHILDREN_COMPARATOR = new ViewElevationComparator();
-            INSETS_HELPER = new CoordinatorLayoutInsetsHelperLollipop();
         } else {
             TOP_SORTED_CHILDREN_COMPARATOR = null;
-            INSETS_HELPER = null;
         }
     }
 
@@ -140,7 +140,6 @@
     };
 
     static final Comparator<View> TOP_SORTED_CHILDREN_COMPARATOR;
-    static final CoordinatorLayoutInsetsHelper INSETS_HELPER;
 
     private final List<View> mDependencySortedChildren = new ArrayList<View>();
     private final List<View> mTempList1 = new ArrayList<>();
@@ -169,6 +168,7 @@
     private Drawable mStatusBarBackground;
 
     private OnHierarchyChangeListener mOnHierarchyChangeListener;
+    private android.support.v4.view.OnApplyWindowInsetsListener mApplyWindowInsetsListener;
 
     private final NestedScrollingParentHelper mNestedScrollingParentHelper =
             new NestedScrollingParentHelper(this);
@@ -201,9 +201,7 @@
         mStatusBarBackground = a.getDrawable(R.styleable.CoordinatorLayout_statusBarBackground);
         a.recycle();
 
-        if (INSETS_HELPER != null) {
-            INSETS_HELPER.setupForWindowInsets(this, new ApplyInsetsListener());
-        }
+        setupForInsets();
         super.setOnHierarchyChangeListener(new HierarchyChangeListener());
     }
 
@@ -333,7 +331,7 @@
         setStatusBarBackground(new ColorDrawable(color));
     }
 
-    private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
+    final WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
         if (!objectEquals(mLastInsets, insets)) {
             mLastInsets = insets;
             mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
@@ -836,6 +834,12 @@
         }
     }
 
+    @Override
+    public void setFitsSystemWindows(boolean fitSystemWindows) {
+        super.setFitsSystemWindows(fitSystemWindows);
+        setupForInsets();
+    }
+
     /**
      * Mark the last known child position rect for the given child view.
      * This will be used when checking if a child view's position has changed between frames.
@@ -2204,6 +2208,7 @@
          *
          * @return The insets supplied, minus any insets that were consumed
          */
+        @NonNull
         public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
                 V child, WindowInsetsCompat insets) {
             return insets;
@@ -2614,14 +2619,6 @@
         }
     }
 
-    private class ApplyInsetsListener
-            implements android.support.v4.view.OnApplyWindowInsetsListener {
-        @Override
-        public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
-            return setWindowInsets(insets);
-        }
-    }
-
     private class HierarchyChangeListener implements OnHierarchyChangeListener {
         @Override
         public void onChildViewAdded(View parent, View child) {
@@ -2690,6 +2687,33 @@
         return ss;
     }
 
+    private void setupForInsets() {
+        if (Build.VERSION.SDK_INT < 21) {
+            return;
+        }
+
+        if (ViewCompat.getFitsSystemWindows(this)) {
+            if (mApplyWindowInsetsListener == null) {
+                mApplyWindowInsetsListener =
+                        new android.support.v4.view.OnApplyWindowInsetsListener() {
+                            @Override
+                            public WindowInsetsCompat onApplyWindowInsets(View v,
+                                    WindowInsetsCompat insets) {
+                                return setWindowInsets(insets);
+                            }
+                        };
+            }
+            // First apply the insets listener
+            ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
+
+            // Now set the sys ui flags to enable us to lay out in the window insets
+            setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+        } else {
+            ViewCompat.setOnApplyWindowInsetsListener(this, null);
+        }
+    }
+
     protected static class SavedState extends AbsSavedState {
         SparseArray<Parcelable> behaviorStates;
 
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
new file mode 100644
index 0000000..54005ba
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+package android.support.design.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doCallRealMethod;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SdkSuppress;
+import android.support.v4.view.WindowInsetsCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.junit.Test;
+
+@MediumTest
+public class CoordinatorLayoutTest extends BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
+
+    public CoordinatorLayoutTest() {
+        super(CoordinatorLayoutActivity.class);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 21)
+    public void testSetFitSystemWindows() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout;
+        final View view = new View(col.getContext());
+
+        // Create a mock which calls the default impl of onApplyWindowInsets()
+        final CoordinatorLayout.Behavior<View> mockBehavior =
+                mock(CoordinatorLayout.Behavior.class);
+        doCallRealMethod().when(mockBehavior)
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+
+        // Assert that the CoL is currently not set to fitSystemWindows
+        assertFalse(col.getFitsSystemWindows());
+
+        // Now add a view with our mocked behavior to the CoordinatorLayout
+        view.setFitsSystemWindows(true);
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams();
+                lp.setBehavior(mockBehavior);
+                col.addView(view, lp);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Now request some insets and wait for the pass to happen
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                col.requestApplyInsets();
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Verify that onApplyWindowInsets() has not been called
+        verify(mockBehavior, never())
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+
+        // Now enable fits system windows and wait for a pass to happen
+        instrumentation.runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                col.setFitsSystemWindows(true);
+            }
+        });
+        instrumentation.waitForIdleSync();
+
+        // Verify that onApplyWindowInsets() has been called with some insets
+        verify(mockBehavior, atLeastOnce())
+                .onApplyWindowInsets(same(col), same(view), any(WindowInsetsCompat.class));
+    }
+
+}