1911642499da7d796aa1e7c19178c3552a590f48bChris Banes/*
2911642499da7d796aa1e7c19178c3552a590f48bChris Banes * Copyright (C) 2015 The Android Open Source Project
3911642499da7d796aa1e7c19178c3552a590f48bChris Banes *
4911642499da7d796aa1e7c19178c3552a590f48bChris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5911642499da7d796aa1e7c19178c3552a590f48bChris Banes * you may not use this file except in compliance with the License.
6911642499da7d796aa1e7c19178c3552a590f48bChris Banes * You may obtain a copy of the License at
7911642499da7d796aa1e7c19178c3552a590f48bChris Banes *
8911642499da7d796aa1e7c19178c3552a590f48bChris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9911642499da7d796aa1e7c19178c3552a590f48bChris Banes *
10911642499da7d796aa1e7c19178c3552a590f48bChris Banes * Unless required by applicable law or agreed to in writing, software
11911642499da7d796aa1e7c19178c3552a590f48bChris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12911642499da7d796aa1e7c19178c3552a590f48bChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13911642499da7d796aa1e7c19178c3552a590f48bChris Banes * See the License for the specific language governing permissions and
14911642499da7d796aa1e7c19178c3552a590f48bChris Banes * limitations under the License.
15911642499da7d796aa1e7c19178c3552a590f48bChris Banes */
16911642499da7d796aa1e7c19178c3552a590f48bChris Banes
1766698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banespackage android.support.v7.widget;
18911642499da7d796aa1e7c19178c3552a590f48bChris Banes
19911642499da7d796aa1e7c19178c3552a590f48bChris Banesimport android.content.Context;
20911642499da7d796aa1e7c19178c3552a590f48bChris Banesimport android.content.ContextWrapper;
21911642499da7d796aa1e7c19178c3552a590f48bChris Banesimport android.content.res.Resources;
225fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banesimport android.os.Build;
23e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banesimport android.support.annotation.NonNull;
245fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banesimport android.support.v7.app.AppCompatDelegate;
255fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banesimport android.support.v7.widget.VectorEnabledTintResources;
26911642499da7d796aa1e7c19178c3552a590f48bChris Banes
279f63864c84e3d48841d553521468c7a27189c4f0Chris Banesimport java.lang.ref.WeakReference;
289f63864c84e3d48841d553521468c7a27189c4f0Chris Banesimport java.util.ArrayList;
299f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
30911642499da7d796aa1e7c19178c3552a590f48bChris Banes/**
31911642499da7d796aa1e7c19178c3552a590f48bChris Banes * A {@link android.content.ContextWrapper} which returns a tint-aware
32911642499da7d796aa1e7c19178c3552a590f48bChris Banes * {@link android.content.res.Resources} instance from {@link #getResources()}.
335fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes *
345fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes * @hide
35911642499da7d796aa1e7c19178c3552a590f48bChris Banes */
365fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banespublic class TintContextWrapper extends ContextWrapper {
37911642499da7d796aa1e7c19178c3552a590f48bChris Banes
389f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
399f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
409f63864c84e3d48841d553521468c7a27189c4f0Chris Banes    public static Context wrap(@NonNull final Context context) {
415fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (shouldWrap(context)) {
429f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            // First check our instance cache
439f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            for (int i = 0, count = sCache.size(); i < count; i++) {
449f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                final WeakReference<TintContextWrapper> ref = sCache.get(i);
459f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                final TintContextWrapper wrapper = ref != null ? ref.get() : null;
469f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                if (wrapper != null && wrapper.getBaseContext() == context) {
479f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                    return wrapper;
489f63864c84e3d48841d553521468c7a27189c4f0Chris Banes                }
499f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            }
509f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            // If we reach here then the cache didn't have a hit, so create a new instance
51604c6317360c5f744cfebb7c23b243358aef58e0Chris Banes            // and add it to the cache
529f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            final TintContextWrapper wrapper = new TintContextWrapper(context);
53604c6317360c5f744cfebb7c23b243358aef58e0Chris Banes            sCache.add(new WeakReference<>(wrapper));
549f63864c84e3d48841d553521468c7a27189c4f0Chris Banes            return wrapper;
55911642499da7d796aa1e7c19178c3552a590f48bChris Banes        }
569f63864c84e3d48841d553521468c7a27189c4f0Chris Banes
57911642499da7d796aa1e7c19178c3552a590f48bChris Banes        return context;
58911642499da7d796aa1e7c19178c3552a590f48bChris Banes    }
59911642499da7d796aa1e7c19178c3552a590f48bChris Banes
605fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    private static boolean shouldWrap(@NonNull final Context context) {
615fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (context instanceof TintContextWrapper
625fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes                || context.getResources() instanceof TintResources
635fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes                || context.getResources() instanceof VectorEnabledTintResources) {
645fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // If the Context already has a TintResources[Experimental] impl, no need to wrap again
655fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // If the Context is already a TintContextWrapper, no need to wrap again
665fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            return false;
675fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
685fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (AppCompatDelegate.isCompatVectorFromResourcesEnabled()
695fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes                && Build.VERSION.SDK_INT > VectorEnabledTintResources.MAX_SDK_WHERE_REQUIRED) {
705fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // If we're running on API 21+ and have the vector resources enabled, there's
715fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // no need to wrap
725fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            return false;
735fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
745fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        // Else, we should wrap
755fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        return true;
765fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    }
775fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes
78cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes    private Resources mResources;
795fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    private final Resources.Theme mTheme;
80cd6e77607caba0b3b26163791a361938afb8b9c5Chris Banes
815fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    private TintContextWrapper(@NonNull final Context base) {
82911642499da7d796aa1e7c19178c3552a590f48bChris Banes        super(base);
835fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes
845fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (VectorEnabledTintResources.shouldBeUsed()) {
855fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // We need to create a copy of the Theme so that the Theme references our Resources
865fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            // instance
875fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            mTheme = getResources().newTheme();
885fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            mTheme.setTo(base.getTheme());
895fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        } else {
905fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            mTheme = null;
915fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
925fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    }
935fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes
945fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    @Override
955fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    public Resources.Theme getTheme() {
965fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        return mTheme == null ? super.getTheme() : mTheme;
975fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    }
985fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes
995fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    @Override
1005fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes    public void setTheme(int resid) {
1015fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        if (mTheme == null) {
1025fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            super.setTheme(resid);
1035fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        } else {
1045fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            mTheme.applyStyle(resid, true);
1055fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes        }
106e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    }
1072cccf609662389d6a23dbc0711d5fb2e826e8c63Chris Banes
108e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    @Override
109e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes    public Resources getResources() {
110e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        if (mResources == null) {
1115fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes            mResources = (mTheme == null)
1125fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes                    ? new TintResources(this, super.getResources())
1135fa121a1f51114f3a2f6d705139e83c0e0aea610Chris Banes                    : new VectorEnabledTintResources(this, super.getResources());
1142cccf609662389d6a23dbc0711d5fb2e826e8c63Chris Banes        }
115e4beadba70aecabbd7f6677943ab7c0b94809b8aChris Banes        return mResources;
1162cccf609662389d6a23dbc0711d5fb2e826e8c63Chris Banes    }
117911642499da7d796aa1e7c19178c3552a590f48bChris Banes}