Workaround TextInputLayout state changes becoming permanent

Also moved the 'tinting' functionality to use color filter.
This seems to work much better on L+ because we're not
wiping out the internal tint, just overriding it with
the filter.

BUG: 25413545

Change-Id: I1d74a8ff817b0b20fcca6d3a1dd2ab2c60539cd1
diff --git a/design/src/android/support/design/widget/DrawableUtils.java b/design/src/android/support/design/widget/DrawableUtils.java
new file mode 100644
index 0000000..1c46e6b
--- /dev/null
+++ b/design/src/android/support/design/widget/DrawableUtils.java
@@ -0,0 +1,99 @@
+/*
+ * 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.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.os.Build;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Caution. Gross hacks ahead.
+ */
+class DrawableUtils {
+
+    private static final String LOG_TAG = "DrawableUtils";
+
+    private static Method sSetConstantStateMethod;
+    private static boolean sSetConstantStateMethodFetched;
+
+    private static Field sDrawableContainerStateField;
+    private static boolean sDrawableContainerStateFieldFetched;
+
+    private DrawableUtils() {}
+
+    static boolean setContainerConstantState(DrawableContainer drawable,
+            Drawable.ConstantState constantState) {
+        if (Build.VERSION.SDK_INT >= 9) {
+            // We can use getDeclaredMethod() on v9+
+            return setContainerConstantStateV9(drawable, constantState);
+        } else {
+            // Else we'll just have to set the field directly
+            return setContainerConstantStateV7(drawable, constantState);
+        }
+    }
+
+    private static boolean setContainerConstantStateV9(DrawableContainer drawable,
+            Drawable.ConstantState constantState) {
+        if (!sSetConstantStateMethodFetched) {
+            try {
+                sSetConstantStateMethod = DrawableContainer.class.getDeclaredMethod(
+                        "setConstantState", DrawableContainer.DrawableContainerState.class);
+                sSetConstantStateMethod.setAccessible(true);
+            } catch (NoSuchMethodException e) {
+                Log.e(LOG_TAG, "Could not fetch setConstantState(). Oh well.");
+            }
+            sSetConstantStateMethodFetched = true;
+        }
+        if (sSetConstantStateMethod != null) {
+            try {
+                sSetConstantStateMethod.invoke(drawable, constantState);
+                return true;
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Could not invoke setConstantState(). Oh well.");
+            }
+        }
+        return false;
+    }
+
+    private static boolean setContainerConstantStateV7(DrawableContainer drawable,
+            Drawable.ConstantState constantState) {
+        if (!sDrawableContainerStateFieldFetched) {
+            try {
+                sDrawableContainerStateField = DrawableContainer.class
+                        .getDeclaredField("mDrawableContainerStateField");
+                sDrawableContainerStateField.setAccessible(true);
+            } catch (NoSuchFieldException e) {
+                Log.e(LOG_TAG, "Could not fetch mDrawableContainerStateField. Oh well.");
+            }
+            sDrawableContainerStateFieldFetched = true;
+        }
+        if (sDrawableContainerStateField != null) {
+            try {
+                sDrawableContainerStateField.set(drawable, constantState);
+                return true;
+            } catch (Exception e) {
+                Log.e(LOG_TAG, "Could not set mDrawableContainerStateField. Oh well.");
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 162643e..b583945 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -22,7 +22,10 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.PorterDuff;
 import android.graphics.Typeface;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.annotation.StyleRes;
@@ -89,6 +92,8 @@
     private boolean mHintAnimationEnabled;
     private ValueAnimatorCompat mAnimator;
 
+    private boolean mHasReconstructedEditTextBackground;
+
     public TextInputLayout(Context context) {
         this(context, null);
     }
@@ -624,17 +629,60 @@
     }
 
     private void updateEditTextBackground() {
+        ensureBackgroundDrawableStateWorkaround();
+
+        final Drawable editTextBackground = mEditText.getBackground();
+        if (editTextBackground == null) {
+            return;
+        }
+
         if (mErrorShown && mErrorView != null) {
-            // Set the EditText's background tint to the error color
-            ViewCompat.setBackgroundTintList(mEditText,
-                    ColorStateList.valueOf(mErrorView.getCurrentTextColor()));
+            // Set a color filter of the error color
+            editTextBackground.setColorFilter(
+                    AppCompatDrawableManager.getPorterDuffColorFilter(
+                            mErrorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
         } else if (mCounterOverflowed && mCounterView != null) {
-            ViewCompat.setBackgroundTintList(mEditText,
-                    ColorStateList.valueOf(mCounterView.getCurrentTextColor()));
+            // Set a color filter of the counter color
+            editTextBackground.setColorFilter(
+                    AppCompatDrawableManager.getPorterDuffColorFilter(
+                            mCounterView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN));
         } else {
-            ViewCompat.setBackgroundTintList(mEditText,
-                    AppCompatDrawableManager.get()
-                            .getTintList(getContext(), R.drawable.abc_edit_text_material));
+            // Else reset the color filter and refresh the drawable state so that the
+            // normal tint is used
+            editTextBackground.clearColorFilter();
+            mEditText.refreshDrawableState();
+        }
+    }
+
+    private void ensureBackgroundDrawableStateWorkaround() {
+        final Drawable bg = mEditText.getBackground();
+        if (bg == null) {
+            return;
+        }
+
+        if (!mHasReconstructedEditTextBackground) {
+            // This is gross. There is an issue in the platform which affects container Drawables
+            // where the first drawable retrieved from resources will propogate any changes
+            // (like color filter) to all instances from the cache. We'll try to workaround it...
+
+            final Drawable newBg = bg.getConstantState().newDrawable();
+
+            if (bg instanceof DrawableContainer) {
+                // If we have a Drawable container, we can try and set it's constant state via
+                // reflection from the new Drawable
+                mHasReconstructedEditTextBackground =
+                        DrawableUtils.setContainerConstantState(
+                                (DrawableContainer) bg, newBg.getConstantState());
+            }
+
+            if (!mHasReconstructedEditTextBackground) {
+                // If we reach here then we just need to set a brand new instance of the Drawable
+                // as the background. This has the unfortunate side-effect of wiping out any
+                // user set padding, but I'd hope that use of custom padding on an EditText
+                // is limited.
+                mEditText.setBackgroundDrawable(newBg);
+                mHasReconstructedEditTextBackground = true;
+            }
         }
     }
 
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index eb04b01..89deb68 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -663,7 +663,7 @@
         return getPorterDuffColorFilter(color, tintMode);
     }
 
-    private static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) {
+    public static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) {
         // First, lets see if the cache already contains the color filter
         PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);