1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v7.widget;
18
19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20
21import android.content.Context;
22import android.content.ContextWrapper;
23import android.content.res.AssetManager;
24import android.content.res.Resources;
25import android.os.Build;
26import android.support.annotation.NonNull;
27import android.support.annotation.RestrictTo;
28
29import java.lang.ref.WeakReference;
30import java.util.ArrayList;
31
32/**
33 * A {@link android.content.ContextWrapper} which returns a tint-aware
34 * {@link android.content.res.Resources} instance from {@link #getResources()}.
35 *
36 * @hide
37 */
38@RestrictTo(LIBRARY_GROUP)
39public class TintContextWrapper extends ContextWrapper {
40
41    private static final Object CACHE_LOCK = new Object();
42    private static ArrayList<WeakReference<TintContextWrapper>> sCache;
43
44    public static Context wrap(@NonNull final Context context) {
45        if (shouldWrap(context)) {
46            synchronized (CACHE_LOCK) {
47                if (sCache == null) {
48                    sCache = new ArrayList<>();
49                } else {
50                    // This is a convenient place to prune any dead reference entries
51                    for (int i = sCache.size() - 1; i >= 0; i--) {
52                        final WeakReference<TintContextWrapper> ref = sCache.get(i);
53                        if (ref == null || ref.get() == null) {
54                            sCache.remove(i);
55                        }
56                    }
57                    // Now check our instance cache
58                    for (int i = sCache.size() - 1; i >= 0; i--) {
59                        final WeakReference<TintContextWrapper> ref = sCache.get(i);
60                        final TintContextWrapper wrapper = ref != null ? ref.get() : null;
61                        if (wrapper != null && wrapper.getBaseContext() == context) {
62                            return wrapper;
63                        }
64                    }
65                }
66                // If we reach here then the cache didn't have a hit, so create a new instance
67                // and add it to the cache
68                final TintContextWrapper wrapper = new TintContextWrapper(context);
69                sCache.add(new WeakReference<>(wrapper));
70                return wrapper;
71            }
72        }
73        return context;
74    }
75
76    private static boolean shouldWrap(@NonNull final Context context) {
77        if (context instanceof TintContextWrapper
78                || context.getResources() instanceof TintResources
79                || context.getResources() instanceof VectorEnabledTintResources) {
80            // If the Context already has a TintResources[Experimental] impl, no need to wrap again
81            // If the Context is already a TintContextWrapper, no need to wrap again
82            return false;
83        }
84        return Build.VERSION.SDK_INT < 21 || VectorEnabledTintResources.shouldBeUsed();
85    }
86
87    private final Resources mResources;
88    private final Resources.Theme mTheme;
89
90    private TintContextWrapper(@NonNull final Context base) {
91        super(base);
92
93        if (VectorEnabledTintResources.shouldBeUsed()) {
94            // We need to create a copy of the Theme so that the Theme references our
95            // new Resources instance
96            mResources = new VectorEnabledTintResources(this, base.getResources());
97            mTheme = mResources.newTheme();
98            mTheme.setTo(base.getTheme());
99        } else {
100            mResources = new TintResources(this, base.getResources());
101            mTheme = null;
102        }
103    }
104
105    @Override
106    public Resources.Theme getTheme() {
107        return mTheme == null ? super.getTheme() : mTheme;
108    }
109
110    @Override
111    public void setTheme(int resid) {
112        if (mTheme == null) {
113            super.setTheme(resid);
114        } else {
115            mTheme.applyStyle(resid, true);
116        }
117    }
118
119    @Override
120    public Resources getResources() {
121        return mResources;
122    }
123
124    @Override
125    public AssetManager getAssets() {
126        // Ensure we're returning assets with the correct configuration.
127        return mResources.getAssets();
128    }
129}