Merge "More tests for background tinting of AppCompatTextView" into mnc-ub-dev
diff --git a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
index 89be82e..f62a11f 100644
--- a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -127,7 +127,7 @@
 
     @Override
     public void destroyItem(ViewGroup container, int position, Object object) {
-        Fragment fragment = (Fragment)object;
+        Fragment fragment = (Fragment) object;
 
         if (mCurTransaction == null) {
             mCurTransaction = mFragmentManager.beginTransaction();
@@ -137,7 +137,8 @@
         while (mSavedState.size() <= position) {
             mSavedState.add(null);
         }
-        mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+        mSavedState.set(position, fragment.isAdded()
+                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
         mFragments.set(position, null);
 
         mCurTransaction.remove(fragment);
diff --git a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
index 0c3c6c5..ad9072b 100644
--- a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -16,8 +16,6 @@
 
 package android.support.v4.app;
 
-import java.util.ArrayList;
-
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.support.v4.view.PagerAdapter;
@@ -25,6 +23,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import java.util.ArrayList;
+
 /**
  * Implementation of {@link android.support.v4.view.PagerAdapter} that
  * uses a {@link Fragment} to manage each page. This class also handles
@@ -123,7 +123,7 @@
 
     @Override
     public void destroyItem(ViewGroup container, int position, Object object) {
-        Fragment fragment = (Fragment)object;
+        Fragment fragment = (Fragment) object;
 
         if (mCurTransaction == null) {
             mCurTransaction = mFragmentManager.beginTransaction();
@@ -133,7 +133,8 @@
         while (mSavedState.size() <= position) {
             mSavedState.add(null);
         }
-        mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+        mSavedState.set(position, fragment.isAdded()
+                ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
         mFragments.set(position, null);
 
         mCurTransaction.remove(fragment);
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index 11acb79..ed99227 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -978,9 +978,7 @@
 
     void populate(int newCurrentItem) {
         ItemInfo oldCurInfo = null;
-        int focusDirection = View.FOCUS_FORWARD;
         if (mCurItem != newCurrentItem) {
-            focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
             oldCurInfo = infoForPosition(mCurItem);
             mCurItem = newCurrentItem;
         }
@@ -1155,7 +1153,7 @@
                     View child = getChildAt(i);
                     ii = infoForChild(child);
                     if (ii != null && ii.position == mCurItem) {
-                        if (child.requestFocus(focusDirection)) {
+                        if (child.requestFocus(View.FOCUS_FORWARD)) {
                             break;
                         }
                     }
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 322bf81..e79f4e3 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -26,6 +26,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Drawable.ConstantState;
 import android.graphics.drawable.DrawableContainer;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.InsetDrawable;
@@ -40,6 +41,7 @@
 import android.support.v4.graphics.ColorUtils;
 import android.support.v4.graphics.drawable.DrawableCompat;
 import android.support.v4.util.ArrayMap;
+import android.support.v4.util.LongSparseArray;
 import android.support.v4.util.LruCache;
 import android.support.v7.appcompat.R;
 import android.util.AttributeSet;
@@ -48,7 +50,7 @@
 import android.util.TypedValue;
 import android.util.Xml;
 
-import java.lang.reflect.Type;
+import java.lang.ref.WeakReference;
 import java.util.WeakHashMap;
 
 import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
@@ -60,25 +62,12 @@
  */
 public final class AppCompatDrawableManager {
 
-    public interface InflateDelegate {
+    private interface InflateDelegate {
         Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
                 @NonNull AttributeSet attrs, @Nullable Resources.Theme theme);
     }
 
-    private static final InflateDelegate VDC_DELEGATE = new InflateDelegate() {
-        @Override
-        public Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
-                @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
-            try {
-                return VectorDrawableCompat.createFromXmlInner(r, parser, attrs, theme);
-            } catch (Exception e) {
-                Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
-                return null;
-            }
-        }
-    };
-
-    private static final String TAG = "TintManager";
+    private static final String TAG = "AppCompatDrawableManager";
     private static final boolean DEBUG = false;
     private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
     private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";
@@ -88,7 +77,12 @@
     public static AppCompatDrawableManager get() {
         if (INSTANCE == null) {
             INSTANCE = new AppCompatDrawableManager();
-            INSTANCE.addDelegate("vector", VDC_DELEGATE);
+
+            if (Build.VERSION.SDK_INT < 21) {
+                // We only want to use the automatic VectorDrawableCompat handling where it's
+                // needed: on devices running before Lollipop
+                INSTANCE.addDelegate("vector", new VdcInflateDelegate());
+            }
         }
         return INSTANCE;
     }
@@ -159,6 +153,10 @@
     private ArrayMap<String, InflateDelegate> mDelegates;
     private SparseArray<String> mKnownDrawableIdTags;
 
+    private final Object mDelegateDrawableCacheLock = new Object();
+    private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>>
+            mDelegateDrawableCaches = new WeakHashMap<>(0);
+
     private TypedValue mTypedValue;
 
     public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
@@ -229,16 +227,14 @@
 
     private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {
         if (mDelegates != null && !mDelegates.isEmpty()) {
-            String cachedTagName = null;
-
             if (mKnownDrawableIdTags != null) {
-                cachedTagName = mKnownDrawableIdTags.get(resId);
+                final String cachedTagName = mKnownDrawableIdTags.get(resId);
                 if (SKIP_DRAWABLE_TAG.equals(cachedTagName)
                         || (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {
                     // If we don't have a delegate for the drawable tag, or we've been set to
                     // skip it, fail fast and return null
                     if (DEBUG) {
-                        Log.d(TAG, "loadDrawableFromDelegates. Skipping drawable "
+                        Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: "
                                 + context.getResources().getResourceName(resId));
                     }
                     return null;
@@ -256,6 +252,18 @@
             final Resources res = context.getResources();
             res.getValue(resId, tv, true);
 
+            final long key = (((long) tv.assetCookie) << 32) | tv.data;
+
+            Drawable dr = getCachedDelegateDrawable(context, key);
+            if (dr != null) {
+                if (DEBUG) {
+                    Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
+                            context.getResources().getResourceName(resId));
+                }
+                // We have a cached drawable, return it!
+                return dr;
+            }
+
             if (tv.string != null && tv.string.toString().endsWith(".xml")) {
                 // If the resource is an XML file, let's try and parse it
                 try {
@@ -271,28 +279,78 @@
                     }
 
                     final String tagName = parser.getName();
-                    if (cachedTagName == null) {
-                        // If we don't already have this cached, add it to the cache
-                        mKnownDrawableIdTags.append(resId, tagName);
-                    }
+                    // Add the tag name to the cache
+                    mKnownDrawableIdTags.append(resId, tagName);
 
                     // Now try and find a delegate for the tag name and inflate if found
                     final InflateDelegate delegate = mDelegates.get(tagName);
                     if (delegate != null) {
-                        return delegate.createFromXmlInner(res, parser, attrs, context.getTheme());
+                        dr = delegate.createFromXmlInner(res, parser, attrs, context.getTheme());
+                    }
+                    if (dr != null) {
+                        // Add it to the drawable cache
+                        dr.setChangingConfigurations(tv.changingConfigurations);
+                        if (addCachedDelegateDrawable(context, key, dr) && DEBUG) {
+                            Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
+                                    context.getResources().getResourceName(resId));
+                        }
                     }
                 } catch (Exception e) {
                     Log.e(TAG, "Exception while inflating drawable", e);
                 }
             }
+            if (dr == null) {
+                // If we reach here then the delegate inflation of the resource failed. Mark it as
+                // bad so we skip the id next time
+                mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
+            }
+            return dr;
         }
 
-        // If we reach here then the delegate inflation of the resource failed. Mark it as
-        // bad so we skip the id next time
-        mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
         return null;
     }
 
+    private Drawable getCachedDelegateDrawable(@NonNull final Context context, final long key) {
+        synchronized (mDelegateDrawableCacheLock) {
+            final LongSparseArray<WeakReference<ConstantState>> cache
+                    = mDelegateDrawableCaches.get(context);
+            if (cache == null) {
+                return null;
+            }
+
+            final WeakReference<ConstantState> wr = cache.get(key);
+            if (wr != null) {
+                // We have the key, and the secret
+                ConstantState entry = wr.get();
+                if (entry != null) {
+                    return entry.newDrawable(context.getResources());
+                } else {
+                    // Our entry has been purged
+                    cache.delete(key);
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean addCachedDelegateDrawable(@NonNull final Context context, final long key,
+            @NonNull final Drawable drawable) {
+        final ConstantState cs = drawable.getConstantState();
+        if (cs != null) {
+            synchronized (mDelegateDrawableCacheLock) {
+                LongSparseArray<WeakReference<ConstantState>> cache
+                        = mDelegateDrawableCaches.get(context);
+                if (cache == null) {
+                    cache = new LongSparseArray<>();
+                    mDelegateDrawableCaches.put(context, cache);
+                }
+                cache.put(key, new WeakReference<ConstantState>(cs));
+            }
+            return true;
+        }
+        return false;
+    }
+
     public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
             @NonNull TintResources resources, @DrawableRes final int resId) {
         Drawable drawable = loadDrawableFromDelegates(context, resId);
@@ -341,7 +399,8 @@
             }
 
             if (DEBUG) {
-                Log.d(TAG, "Tinted Drawable: " + context.getResources().getResourceName(resId) +
+                Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted "
+                        + context.getResources().getResourceName(resId) +
                         " with color: #" + Integer.toHexString(color));
             }
             return true;
@@ -349,14 +408,14 @@
         return false;
     }
 
-    public final void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
+    private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
         if (mDelegates == null) {
             mDelegates = new ArrayMap<>();
         }
         mDelegates.put(tagName, delegate);
     }
 
-    public final void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
+    private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
         if (mDelegates != null && mDelegates.get(tagName) == delegate) {
             mDelegates.remove(tagName);
         }
@@ -730,7 +789,7 @@
             return Build.VERSION.SDK_INT >= 14;
         } else if (drawable instanceof DrawableContainer) {
             // If we have a DrawableContainer, let's traverse it's child array
-            final Drawable.ConstantState state = drawable.getConstantState();
+            final ConstantState state = drawable.getConstantState();
             if (state instanceof DrawableContainer.DrawableContainerState) {
                 final DrawableContainer.DrawableContainerState containerState =
                         (DrawableContainer.DrawableContainerState) state;
@@ -772,4 +831,17 @@
         }
         d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));
     }
+
+    private static class VdcInflateDelegate implements InflateDelegate {
+        @Override
+        public Drawable createFromXmlInner(@NonNull Resources r, @NonNull XmlPullParser parser,
+                @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
+            try {
+                return VectorDrawableCompat.createFromXmlInner(r, parser, attrs, theme);
+            } catch (Exception e) {
+                Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
+                return null;
+            }
+        }
+    }
 }
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index 2503fe4..edbd3fa 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -19,19 +19,36 @@
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
 import android.support.annotation.NonNull;
 
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
 /**
  * A {@link android.content.ContextWrapper} which returns a tint-aware
  * {@link android.content.res.Resources} instance from {@link #getResources()}.
  */
 class TintContextWrapper extends ContextWrapper {
 
-    public static Context wrap(Context context) {
+    private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
+
+    public static Context wrap(@NonNull final Context context) {
         if (!(context instanceof TintContextWrapper)) {
-            context = new TintContextWrapper(context);
+            // First check our instance cache
+            for (int i = 0, count = sCache.size(); i < count; i++) {
+                final WeakReference<TintContextWrapper> ref = sCache.get(i);
+                final TintContextWrapper wrapper = ref != null ? ref.get() : null;
+                if (wrapper != null && wrapper.getBaseContext() == context) {
+                    return wrapper;
+                }
+            }
+            // If we reach here then the cache didn't have a hit, so create a new instance
+            // and add it to the cahce
+            final TintContextWrapper wrapper = new TintContextWrapper(context);
+            sCache.add(new WeakReference<TintContextWrapper>(wrapper));
+            return wrapper;
         }
+
         return context;
     }